-
Notifications
You must be signed in to change notification settings - Fork 20
/
api.txt
356 lines (238 loc) · 12.6 KB
/
api.txt
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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
NR ICE INTERNAL API
$Id: api.txt,v 1.1.1.1 2008/04/28 17:39:19 ekr Exp $
CAVEATS
This is our internal API. It is not really intended to be called
by customers. Among the failings are:
- excessive complexity and flexibility
- no separation between internal and external structures
and functions (it's all internal)
In our initial deployment we wrote a new C++ API for the customer
on top of this API. We would do something similar for other
customers, but hopefully with a more general API.
OVERVIEW
The most important thing to know here is that everything in the API
is event driven in a single thread. Any action which entails any
delay requires a callback function which will be called when the
action completes. So, this is standard event-driven programming.
The ICE API exports the following major data structures:
- nr_ice_ctx -- a single session (e.g., phone call)
- nr_ice_peer_ctx -- a fork of a session
- nr_ice_media_stream -- a media stream and its components
The basic sequence of events here from the offeror's perspective) is
as follows:
1. instantiate an nr_ice_ctx using nr_ice_ctx_create()
2. set up the media streams you will use by calling
nr_ice_media_stream_create(). Each call returns a pointer
to that media stream.
3. initialize the ice ctx by calling nr_ice_initialize().
The initialization stage is where candidates are
gathered, which can take some time, so you need to provide
a completion callback.
4. Once the completion callback fires, you have a complete
list of candidates. You can then export the SDP fragment
(the new attributes for each media stream) using
nr_ice_get_global_attributes() and nr_ice_media_stream_get_attributes().
5. The offeror needs to send the SDP to the other side.
Again, this entails delay.
6. When an answer is received, the offerer needs to instantiate
an nr_ice_peer_ctx using nr_ice_peer_ctx_create()
to represent this answer. There is one nr_ice_peer_ctx per fork.
There are a lot of different events that can occur on a
nr_ice_peer_ctx, so you need to provide a handler obj.
7. You then load the peer's answer into the nr_ice_peer_ctx using
nr_ice_peer_ctx_parse_{global,stream}_attributes().
8. At this point you are ready to start the connectivity
checks, using nr_ice_peer_ctx_start_checks(). Again,
this happens asynchronously.
9. During the connectivity checks, a variety of events happen
notifying you of stream readiness, letting you choose which
of multiple successful pairs to use (in regular nomination
mode), etc.
10. Eventually, all the checks complete and the ice_completed()
handler function is called. At this point, the connectivity
checks are complete.
11. Once you have decided which fork to accept, you call
nr_ice_ctx_finalize() with that fork's nr_ice_peer_ctx,
nr_ice_peer_ctx_destroy() on the unused nr_ice_peer_ctxs.
This cleans up the various file descriptors, etc. that
won't be used.
12. In order to read and write media, you need to talk to
the media streams. To write you call nr_ice_media_stream_end().
When data is read, the msg_recvd() handler function is
invoked.
13. Once the call is terminated, call nr_ice_peer_ctx_destroy()
and nr_ice_ctx_destroy().
The answerer's perspective is similar, except that you only have
one nr_ice_peer_ctx and you create it at the time of offer receipt.
MORE DETAILED DOCUMENTATION
Our general convention is that all successful functions return 0.
Nonzero means error. A list can be found in nrappkit.
int nr_ice_ctx_create(char *label, UINT4 flags, nr_ice_ctx **ctxp);
#define NR_ICE_CTX_FLAGS_OFFERER 1
#define NR_ICE_CTX_FLAGS_ANSWERER (1<<1)
#define NR_ICE_CTX_FLAGS_AGGRESSIVE_NOMINATION (1<<2)
#define NR_ICE_CTX_FLAGS_LITE (1<<3)
Creates an ICE ctx.
label - a freeform string used for debugging (IN)
flags - the mode you want to operate in (IN)
ctxp - the created context (OUT)
int nr_ice_media_stream_create(struct nr_ice_ctx_ *ctx,char *label, int components, nr_ice_media_stream **streamp);
Creates a media stream with a given number of components.
ctx - the ctx to create the media stream with (IN)
label - a freeform string used for debugging (IN)
components - the number of components associated with the stream (IN)
streamp - the created stream (OUT)
int nr_ice_initialize(nr_ice_ctx *ctx, NR_async_cb done_cb, void *cb_arg);
Start initializing the ICE ctx ctx. cb(0,0,cb_arg) is called when done.
int nr_ice_get_global_attributes(nr_ice_ctx *ctx,char ***attrsp, int *attrctp);
Returns the global (per-session) ICE attributes (a-lines) as an array
of char *s
ctx - the ctx get the attribute from (IN)
attrsp - returns an array of char *s, one per attribute (OUT)
attrctp - returns the number of attributes (OUT)
Each attr (char *) and the array (char **) must be freed by the caller.
int nr_ice_media_stream_get_attributes(nr_ice_media_stream *stream, char ***attrsp,int *attrctp);
Returns the per media stream attributes as an array of char *s
stream - the stream to get get the attributes from (IN)
attrsp - returns an array of char *s, one per attribute (OUT)
attrctp - returns the number of attributes (OUT)
Each attr (char *) and the array (char **) must be freed by the caller.
int nr_ice_peer_ctx_create(nr_ice_ctx *ctx, nr_ice_handler *handler,char *label, nr_ice_peer_ctx **pctxp);
Create a new peer context
ctx - the ctx to create the peer context with (IN)
handler - the handler object to call callbacks on (IN)
label - a freeform string used for debugging (IN)
pctxp - the created context (OUT)
Here's the handler defn. The comments should be self explanatory:
typedef struct nr_ice_handler_vtbl_ {
/* The checks on this media stream are done. The handler needs to
select a single pair to proceed with (regular nomination).
Once this returns the check starts and the pair can be
written on. Use nr_ice_candidate_pair_select() to perform the
selection.
TODO: !ekr! is this right?
*/
int (*select_pair)(void *obj,nr_ice_media_stream *stream,
int component_id, nr_ice_cand_pair **potentials,int potential_ct);
/* This media stream is ready to read/write (aggressive nomination).
May be called again if the nominated pair changes due to
ICE instability. TODO: !ekr! think about this
*/
int (*stream_ready)(void *obj, nr_ice_media_stream *stream);
/* This media stream has failed */
int (*stream_failed)(void *obj, nr_ice_media_stream *stream);
/* ICE is completed for this peer ctx
if need_update is nonzero, a SIP update is required
*/
int (*ice_completed)(void *obj, nr_ice_peer_ctx *pctx);
/* A message was delivered to us */
int (*msg_recvd)(void *obj, nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, int component_id, UCHAR *msg, int len);
} nr_ice_handler_vtbl;
typedef struct nr_ice_handler_ {
void *obj;
nr_ice_handler_vtbl *vtbl;
} nr_ice_handler;
We don't care how this is allocated, but obj should be unique for
each peer_ctx, unless you want to maintain a dictionary of
peer_ctx and their corresponding state. We don't look at obj.
int nr_ice_peer_ctx_parse_global_attributes(nr_ice_peer_ctx *pctx, char **attrs, int attr_ct);
Parses the global (per session) attributes.
pctx - the pctx to which these attributes apply (IN)
attrs - an array of null-terminated char *s, one per attr (IN)
attr_ct - the number of attrs (IN)
int nr_ice_peer_ctx_parse_stream_attributes(nr_ice_peer_ctx *pctx, nr_ice_media_stream *stream, char **attrs, int attr_ct);
Parses the stream attributes.
pctx - the pctx to which these attributes apply (IN)
stream - the stream to which these attributes apply (IN)
attrs - an array of null-terminated char *s, one per attr (IN)
attr_ct - the number of attrs (OUT)
int nr_ice_peer_ctx_start_checks(nr_ice_peer_ctx *pctx);
Start ICE checks on pctx pctx.
int nr_ice_candidate_pair_select(nr_ice_cand_pair *pair);
Select a candidate pair for use. This is a bit confusing and
requires a detour into the nomination logic. There are two
kinds of ICE nomination: regular and aggressive. In aggressive
nomination, ICE automatically picks the first working candidate
pair and just tells you. In regular nomination, the app is
expected to pick. Every time something changes we call you
back with the current state of all the candidate pairs and
ask "are you ready to select yet". If you are, you call
nr_ice_candidate_pair_select() on the chosen pair.
Note that regular nomination is incredibly slow if you let
things run to completion (all pairs succeeded or failed.)
We recommend that you set a timer at first success and
when that timer expires select the best choice.
int nr_ice_ctx_finalize(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx);
Cleans up resources that ICE now knows it won't use.
This also closes sockets etc., so we don't bother to
respond to STUN checks on unused pairs.
ctx - the context to finalize (IN)
pctx - the fork that was chosen (IN)
int nr_ice_peer_ctx_destroy(nr_ice_peer_ctx **pctxp);
Cleans up a pctx and all its resources. Note that this is safe
to call on every pctx but the chosen fork.
pctxp - a pointer to the pctx to destroy. *pctxp is set to zero on return
(IN/OUT)
int nr_ice_media_stream_send(nr_ice_peer_ctx *pctx,nr_ice_media_stream *str, int component, UCHAR *data, int len);
Write data on a stream on the chosen pair.
pctx - the associated peer ctx (IN)
stream - the stream (IN)
component - the component number (IN)
data - the data to write (IN)
len - the length of the data (IN)
int nr_ice_ctx_destroy(nr_ice_peer_ctx **ctxp);
Cleans up a ctx and all its resources. Note that this is safe
to call on every ctx but the chosen fork.
ctxp - a pointer to the ctx to destroy. *ctxp is set to zero on return
(IN/OUT)
RELATIONSHIP TO NRAPPKIT
This implementation is built on nrappkit, our generic application
toolkit, for things like events, logging, configuration management,
etc. nrapplkit is open source (on Sourceforge) but we ship the most
recent version with the ICE stack. We have pretty extensive
training slides for nrappkit's functionality we can provide if you
need it.
EVENTS/CALLBACKS/ETC.
There's a complicated event system (in nrappkit), but all you have to
do as a practical matter is periodically service the event
loop. Here's an example:
/* Inner loop */
for(;;){
int events; /* Unused */
r=NR_async_event_wait(&events);
if(r==R_EOD)
break;
}
NR_async_wait() returns R_EOD when no events are registered.
This should obviously never happen unless the thread is ready
to exit.
TUNING PARAMS
There are a lot of tuning params. They are all stored in a
keyword/value database (the registry). You can have a system
global registry process or have it be local to your process.
Here's a tuning param list:
#define NR_STUN_REG_PREF_CLNT_RETRANSMIT_TIMEOUT "stun.client.retransmission_timeout"
#define NR_STUN_REG_PREF_CLNT_RETRANSMIT_BACKOFF "stun.client.retransmission_backoff_factor"
#define NR_STUN_REG_PREF_CLNT_MAXIMUM_TRANSMITS "stun.client.maximum_transmits"
#define NR_STUN_REG_PREF_CLNT_FINAL_RETRANSMIT_BACKOFF "stun.client.final_retransmit_backoff"
#define NR_STUN_REG_PREF_ADDRESS_PRFX "stun.address"
#define NR_STUN_REG_PREF_SERVER_NAME "stun.server_name"
#define NR_ICE_REG_PREF_TYPE_HOST "ice.pref.type.host"
#define NR_ICE_REG_PREF_TYPE_RELAYED "ice.pref.type.relayed"
#define NR_ICE_REG_PREF_TYPE_SRV_RFLX "ice.pref.type.srv_rflx"
#define NR_ICE_REG_PREF_TYPE_PEER_RFLX "ice.pref.type.peer_rflx"
#define NR_ICE_REG_PREF_INTERFACE_PRFX "ice.pref.interface"
#define NR_ICE_REG_SUPPRESS_INTERFACE_PRFX "ice.suppress.interface"
#define NR_ICE_REG_STUN_SRV_PRFX "ice.stun.server"
#define NR_ICE_REG_STUN_SRV_ADDR "addr"
#define NR_ICE_REG_STUN_SRV_PORT "port"
#define NR_ICE_REG_TURN_SRV_PRFX "ice.turn.server"
#define NR_ICE_REG_TURN_SRV_ADDR "addr"
#define NR_ICE_REG_TURN_SRV_PORT "port"
#define NR_ICE_REG_TURN_SRV_BANDWIDTH "bandwidth"
#define NR_ICE_REG_TURN_SRV_LIFETIME "lifetime"
#define NR_ICE_REG_TURN_SRV_USERNAME "username"
#define NR_ICE_REG_TURN_SRV_PASSWORD "password"
#define NR_ICE_REG_KEEPALIVE_TIMER "ice.keepalive_timer"
This should give you the flavor. We'll explain them and how to use them
if/when you need them.