Skip to content

Commit 4373597

Browse files
committed
net: lib: midi2: new Network MIDI 2.0 host stack
Add a new network protocol for MIDI2.0 over the network, using UDP sockets. This allows Zephyr to host a UMP endpoint on the network, which can be invited by UMP clients to exchange MIDI2.0 data. Signed-off-by: Titouan Christophe <[email protected]>
1 parent d02eb5e commit 4373597

File tree

6 files changed

+1137
-0
lines changed

6 files changed

+1137
-0
lines changed

include/zephyr/net/midi2.h

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/*
2+
* Copyright (c) 2025 Titouan Christophe
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
#ifndef ZEPHYR_INCLUDE_NET_MIDI2_H_
7+
#define ZEPHYR_INCLUDE_NET_MIDI2_H_
8+
9+
/**
10+
* @defgroup net_midi2 Network MIDI 2.0
11+
* @since 4.3
12+
* @version 0.1.0
13+
* @ingroup networking
14+
* @{
15+
*/
16+
17+
/**
18+
* @defgroup netmidi10 User Datagram Protocol for Universal MIDI Packets
19+
* @ingroup net_midi2
20+
* @{
21+
* @details Definitions based on the following document
22+
* <a href="https://midi.org/network-midi-2-0">
23+
* User Datagram Protocol for Universal MIDI Packets
24+
* Network MIDI 2.0 (UDP) Transport Specification - Document version 1.0
25+
* (MIDI Association Document: M2-124-UM)
26+
* </a>
27+
* @}
28+
*/
29+
30+
31+
#include <stdint.h>
32+
#include <zephyr/audio/midi.h>
33+
#include <zephyr/net/socket.h>
34+
#include <zephyr/posix/poll.h>
35+
36+
/**
37+
* Size, in bytes, of the nonce sent to the client for authentication
38+
* @see netmidi10: 6.7 Invitation Reply: Authentication Required
39+
*/
40+
#define NETMIDI2_NONCE_SIZE 16
41+
42+
/**
43+
* @brief Statically declare a Network (UDP) MIDI 2.0 endpoint host
44+
* @param _var_name The name of the variable holding the server data
45+
* @param _ep_name The UMP endpoint name
46+
* @param _piid The UMP Product Instance ID. If NULL,
47+
* HWINFO device ID will be used at runtime.
48+
* @param _port The UDP port to listen to, or 0 for automatic assignment
49+
*/
50+
#define NETMIDI2_EP_DEFINE(_var_name, _ep_name, _piid, _port) \
51+
static struct netmidi2_ep _var_name = { \
52+
.name = (_ep_name), \
53+
.piid = (_piid), \
54+
.addr4.sin_port = (_port), \
55+
.auth_type = NETMIDI2_AUTH_NONE, \
56+
}
57+
58+
/**
59+
* @brief Statically declare a Network (UDP) MIDI 2.0 endpoint host,
60+
* with a predefined shared secret key for authentication
61+
* @param _var_name The name of the variable holding the server data
62+
* @param _ep_name The UMP endpoint name
63+
* @param _piid The UMP Product Instance ID. If NULL,
64+
* HWINFO device ID will be used at runtime.
65+
* @param _port The UDP port to listen to, or 0 for automatic assignment
66+
* @param _secret The shared secret key clients must provide to connect
67+
*/
68+
#define NETMIDI2_EP_DEFINE_WITH_AUTH(_var_name, _ep_name, _piid, _port, _secret) \
69+
static struct netmidi2_ep _var_name = { \
70+
.name = (_ep_name), \
71+
.piid = (_piid), \
72+
.addr4.sin_port = (_port), \
73+
.auth_type = NETMIDI2_AUTH_SHARED_SECRET, \
74+
.shared_auth_secret = (_secret), \
75+
}
76+
77+
/**
78+
* @brief Statically declare a Network (UDP) MIDI 2.0 endpoint host,
79+
* with a predefined list of users/passwords for authentication
80+
* @param _var_name The name of the variable holding the server data
81+
* @param _ep_name The UMP endpoint name
82+
* @param _piid The UMP Product Instance ID. If NULL,
83+
* HWINFO device ID will be used at runtime.
84+
* @param _port The UDP port to listen to, or 0 for automatic assignment
85+
* @param ... The username/password pairs (struct netmidi2_user)
86+
*
87+
* Example usage:
88+
* @code
89+
* NETMIDI2_EP_DEFINE_WITH_USERS(my_server, "endpoint", NULL, 0,
90+
* {.name="user1", .password="passwd1"},
91+
* {.name="user2", .password="passwd2"})
92+
* @endcode
93+
*/
94+
#define NETMIDI2_EP_DEFINE_WITH_USERS(_var_name, _ep_name, _piid, _port, ...) \
95+
static const struct netmidi2_userlist users_of_##_var_name = { \
96+
.n_users = ARRAY_SIZE(((struct netmidi2_user []) { __VA_ARGS__ })), \
97+
.users = { __VA_ARGS__ }, \
98+
}; \
99+
static struct netmidi2_ep _var_name = { \
100+
.name = (_ep_name), \
101+
.piid = (_piid), \
102+
.addr4.sin_port = (_port), \
103+
.auth_type = NETMIDI2_AUTH_USER_PASSWORD, \
104+
.userlist = &users_of_##_var_name, \
105+
}
106+
107+
struct netmidi2_ep;
108+
109+
/**
110+
* @brief A username/password pair for user-based authentication
111+
*/
112+
struct netmidi2_user {
113+
/** The user name for authentication */
114+
const char *name;
115+
/** The password for authentication */
116+
const char *password;
117+
};
118+
119+
/**
120+
* @brief A list of users for user-based authentication
121+
*/
122+
struct netmidi2_userlist {
123+
/** Number of users in the list */
124+
size_t n_users;
125+
/** The user/password pairs */
126+
const struct netmidi2_user users[];
127+
};
128+
129+
/**
130+
* @brief A Network MIDI2 session, representing a connection to a peer
131+
*/
132+
struct netmidi2_session {
133+
/**
134+
* State of this session
135+
* @see netmidi10: 6.1 Session States
136+
*/
137+
enum {
138+
/** The session is not in use */
139+
NETMIDI2_SESSION_NOT_INITIALIZED = 0,
140+
/** Device may be aware of each other (e.g. through mDNS),
141+
* however neither device has sent an Invitation.
142+
* The two Devices are not in a Session.
143+
*/
144+
NETMIDI2_SESSION_IDLE,
145+
/** Client has sent Invitation, however the Host has not
146+
* accepted the Invitation. A Bye Command has not been
147+
* sent or received.
148+
*/
149+
NETMIDI2_SESSION_PENDING_INVITATION,
150+
/** Host has replied with Invitation Reply: Authentication
151+
* Required or Invitation Reply: User Authentication Required.
152+
* No timeout has yet occurred. This state is different from
153+
* Idle, because the Client and Host need to store the Nonce.
154+
*/
155+
NETMIDI2_SESSION_AUTH_REQUIRED,
156+
/** Invitation accepted, UMP Commands can be exchanged. */
157+
NETMIDI2_SESSION_ESTABLISHED,
158+
/** One Device has sent the Session Reset Command and is
159+
* waiting for Session Reset Reply, and a timeout has not
160+
* yet occurred.
161+
*/
162+
NETMIDI2_SESSION_PENDING_RESET,
163+
/** one Endpoint has sent Bye and is waiting for Bye Reply,
164+
* and a timeout has not yet occurred.
165+
*/
166+
NETMIDI2_SESSION_PENDING_BYE,
167+
} state;
168+
/** Sequence number of the next universal MIDI packet to send */
169+
uint16_t tx_ump_seq;
170+
/** Sequence number of the next universal MIDI packet to receive */
171+
uint16_t rx_ump_seq;
172+
/** Remote address of the peer */
173+
struct sockaddr addr;
174+
/** Length of the peer's remote address */
175+
socklen_t addr_len;
176+
/** The Network MIDI2 endpoint to which this session belongs */
177+
struct netmidi2_ep *ep;
178+
#if defined(CONFIG_NETMIDI2_HOST_AUTH) || defined(__DOXYGEN__)
179+
/** The username to which this session belongs */
180+
const struct netmidi2_user *user;
181+
/** The crypto nonce used to authorize this session */
182+
char nonce[NETMIDI2_NONCE_SIZE];
183+
#endif
184+
/** The transmission buffer for that peer */
185+
struct net_buf *tx_buf;
186+
/** The transmission work for that peer */
187+
struct k_work tx_work;
188+
};
189+
190+
/**
191+
* @brief Type of authentication in Network MIDI2
192+
*/
193+
enum netmidi2_auth_type {
194+
/** No authentication required */
195+
NETMIDI2_AUTH_NONE,
196+
/** Authentication with a shared secret key */
197+
NETMIDI2_AUTH_SHARED_SECRET,
198+
/** Authentication with username and password */
199+
NETMIDI2_AUTH_USER_PASSWORD,
200+
};
201+
202+
/**
203+
* @brief A Network MIDI2.0 Endpoint
204+
*/
205+
struct netmidi2_ep {
206+
/** The endpoint name */
207+
const char *name;
208+
/** The endpoint product instance id */
209+
const char *piid;
210+
/** The local endpoint address */
211+
union {
212+
struct sockaddr addr;
213+
struct sockaddr_in addr4;
214+
struct sockaddr_in6 addr6;
215+
};
216+
/** The listening socket wrapped in a poll descriptor */
217+
struct pollfd pollsock;
218+
/** The function to call when data is received from a client */
219+
void (*rx_packet_cb)(struct netmidi2_session *session,
220+
const struct midi_ump ump);
221+
/** List of peers to this endpoint */
222+
struct netmidi2_session peers[CONFIG_NETMIDI2_HOST_MAX_CLIENTS];
223+
/** The type of authentication required to establish a session
224+
* with this host endpoint
225+
*/
226+
enum netmidi2_auth_type auth_type;
227+
#if defined(CONFIG_NETMIDI2_HOST_AUTH) || defined(__DOXYGEN__)
228+
union {
229+
/** A shared authentication key */
230+
const char *shared_auth_secret;
231+
/** A list of users/passwords */
232+
const struct netmidi2_userlist *userlist;
233+
};
234+
#endif
235+
};
236+
237+
/**
238+
* @brief Start hosting a network (UDP) Universal MIDI Packet endpoint
239+
* @param ep The network endpoint to start
240+
* @return 0 on success, -errno on error
241+
*/
242+
int netmidi2_host_ep_start(struct netmidi2_ep *ep);
243+
244+
/**
245+
* @brief Send a Universal MIDI Packet to all clients connected to the endpoint
246+
* @param ep The endpoint
247+
* @param[in] ump The packet to send
248+
*/
249+
void netmidi2_broadcast(struct netmidi2_ep *ep, const struct midi_ump ump);
250+
251+
/**
252+
* @brief Send a Universal MIDI Packet to a single client
253+
* @param sess The session identifying the single client
254+
* @param[in] ump The packet to send
255+
*/
256+
void netmidi2_send(struct netmidi2_session *sess, const struct midi_ump ump);
257+
258+
/** @} */
259+
260+
#endif

subsys/net/lib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ add_subdirectory_ifdef(CONFIG_NET_DHCPV6 dhcpv6)
2121
add_subdirectory_ifdef(CONFIG_PROMETHEUS prometheus)
2222
add_subdirectory_ifdef(CONFIG_WIFI_CREDENTIALS wifi_credentials)
2323
add_subdirectory_ifdef(CONFIG_OCPP ocpp)
24+
add_subdirectory_ifdef(CONFIG_NETMIDI2_HOST midi2)
2425

2526
if (CONFIG_NET_DHCPV4 OR CONFIG_NET_DHCPV4_SERVER)
2627
add_subdirectory(dhcpv4)

subsys/net/lib/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ source "subsys/net/lib/dns/Kconfig"
99

1010
source "subsys/net/lib/latmon/Kconfig"
1111

12+
source "subsys/net/lib/midi2/Kconfig"
13+
1214
source "subsys/net/lib/mqtt/Kconfig"
1315

1416
source "subsys/net/lib/mqtt_sn/Kconfig"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) 2025 Titouan Christophe
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
zephyr_library()
5+
6+
zephyr_library_sources(netmidi2.c)

subsys/net/lib/midi2/Kconfig

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2025 Titouan Christophe
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config NETMIDI2_HOST
5+
bool "Network MIDI2 (UDP) host [EXPERIMENTAL]"
6+
select NET_UDP
7+
select NET_SOCKETS
8+
select NET_SOCKETS_SERVICE
9+
depends on NET_IPV4 || NET_IPV6
10+
imply NET_IPV4_MAPPING_TO_IPV6 if NET_IPV4 && NET_IPV6
11+
help
12+
Host library of User Datagram Protocol for Universal MIDI Packets.
13+
Provides the following features:
14+
- Exposing an UMP endpoint over UDP
15+
- Accepting inbound client invitations
16+
- Sending/Receiving Universal MIDI packets
17+
Following "Network MIDI 2.0 (UDP) Transport Specification" v1.0
18+
19+
if NETMIDI2_HOST
20+
config NETMIDI2_HOST_MAX_CLIENTS
21+
int "Maximum number of clients supported by the Network MIDI2 host"
22+
default 5
23+
24+
config NETMIDI2_HOST_AUTH
25+
bool "Support for authentication (shared key or user/password)"
26+
select MBEDTLS
27+
select MBEDTLS_CIPHER_CCM_ENABLED
28+
select CRYPTO
29+
select CRYPTO_MBEDTLS_SHIM
30+
31+
module=NET_MIDI2
32+
module-dep=NET_LOG
33+
module-str=Log level for network MIDI2
34+
module-help=Enables midi2 debug messages.
35+
source "subsys/net/Kconfig.template.log_config.net"
36+
endif

0 commit comments

Comments
 (0)