From 7ca74b399e51d6f2cc42b6d5951764c1dfd031e0 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Thu, 31 Jan 2013 13:00:29 -0500 Subject: [PATCH] use iovec based sdp rewriting --- daemon/bencode.c | 75 +++++++++++++++++++++++-------- daemon/bencode.h | 34 +++++++++----- daemon/call.c | 83 +++++++++++++++++++++------------- daemon/call.h | 2 +- daemon/control_ng.c | 3 ++ daemon/sdp.c | 107 ++++++++++++++++++++++++++------------------ daemon/sdp.h | 14 +++++- 7 files changed, 215 insertions(+), 103 deletions(-) diff --git a/daemon/bencode.c b/daemon/bencode.c index a873a83e6..66a5e8cec 100644 --- a/daemon/bencode.c +++ b/daemon/bencode.c @@ -189,27 +189,47 @@ static void __bencode_container_add(bencode_item_t *parent, bencode_item_t *chil } } -bencode_item_t *bencode_string_len(bencode_buffer_t *buf, const char *s, int len) { +static bencode_item_t *__bencode_string_alloc(bencode_buffer_t *buf, const void *base, + int str_len, int iov_len, int iov_cnt, bencode_type_t type) +{ bencode_item_t *ret; int len_len; - assert((len <= 99999) && (len >= 0)); - ret = __bencode_item_alloc(buf, strlen(s) + 7); + assert((str_len <= 99999) && (str_len >= 0)); + ret = __bencode_item_alloc(buf, 7); if (!ret) return NULL; - len_len = sprintf(ret->__buf, "%d:", len); + len_len = sprintf(ret->__buf, "%d:", str_len); - ret->type = BENCODE_STRING; + ret->type = type; ret->iov[0].iov_base = ret->__buf; ret->iov[0].iov_len = len_len; - ret->iov[1].iov_base = (void *) s; - ret->iov[1].iov_len = len; - ret->iov_cnt = 2; - ret->str_len = len_len + len; + ret->iov[1].iov_base = (void *) base; + ret->iov[1].iov_len = iov_len; + ret->iov_cnt = iov_cnt + 1; + ret->str_len = len_len + str_len; return ret; } +bencode_item_t *bencode_string_len(bencode_buffer_t *buf, const char *s, int len) { + return __bencode_string_alloc(buf, s, len, len, 1, BENCODE_STRING); +} + +bencode_item_t *bencode_string_iovec(bencode_buffer_t *buf, const struct iovec *iov, int iov_cnt, int str_len) { + int i; + + if (iov_cnt < 0) + return NULL; + if (str_len < 0) { + str_len = 0; + for (i = 0; i < iov_cnt; i++) + str_len += iov[i].iov_len; + } + + return __bencode_string_alloc(buf, iov, str_len, iov_cnt, iov_cnt, BENCODE_IOVEC); +} + bencode_item_t *bencode_integer(bencode_buffer_t *buf, long long int i) { bencode_item_t *ret; int alen, rlen; @@ -259,12 +279,20 @@ bencode_item_t *bencode_list_add(bencode_item_t *list, bencode_item_t *item) { return item; } +static int __bencode_iovec_cpy(struct iovec *out, const struct iovec *in, int num) { + out -= num; + memcpy(out, in, num * sizeof(*out)); + return num; +} + static int __bencode_iovec_dump_rev(struct iovec *out, bencode_item_t *item) { bencode_item_t *child; struct iovec *orig = out; - if (item->iov[1].iov_base) - *--out = item->iov[1]; + if (item->type == BENCODE_IOVEC) + out -= __bencode_iovec_cpy(out, item->iov[1].iov_base, item->iov[1].iov_len); + else if (item->iov[1].iov_base) + out -= __bencode_iovec_cpy(out, &item->iov[1], 1); child = item->child; while (child) { @@ -273,20 +301,32 @@ static int __bencode_iovec_dump_rev(struct iovec *out, bencode_item_t *item) { } assert(item->iov[0].iov_base != NULL); - *--out = item->iov[0]; + out -= __bencode_iovec_cpy(out, &item->iov[0], 1); assert((orig - out) == item->iov_cnt); return item->iov_cnt; } +static int __bencode_str_cpy(char *out, const struct iovec *in, int num) { + char *orig = out; + + in += num; + while (--num >= 0) { + in--; + out -= in->iov_len; + memcpy(out, in->iov_base, in->iov_len); + } + return orig - out; +} + static int __bencode_str_dump_rev(char *out, bencode_item_t *item) { bencode_item_t *child; char *orig = out; - if (item->iov[1].iov_base) { - out -= item->iov[1].iov_len; - memcpy(out, item->iov[1].iov_base, item->iov[1].iov_len); - } + if (item->type == BENCODE_IOVEC) + out -= __bencode_str_cpy(out, item->iov[1].iov_base, item->iov[1].iov_len); + else if (item->iov[1].iov_base) + out -= __bencode_str_cpy(out, &item->iov[1], 1); child = item->child; while (child) { @@ -295,8 +335,7 @@ static int __bencode_str_dump_rev(char *out, bencode_item_t *item) { } assert(item->iov[0].iov_base != NULL); - out -= item->iov[0].iov_len; - memcpy(out, item->iov[0].iov_base, item->iov[0].iov_len); + out -= __bencode_str_cpy(out, &item->iov[0], 1); assert((orig - out) == item->str_len); return item->str_len; diff --git a/daemon/bencode.h b/daemon/bencode.h index 342a61d9d..ada44f59e 100644 --- a/daemon/bencode.h +++ b/daemon/bencode.h @@ -38,6 +38,7 @@ enum bencode_type { BENCODE_INTEGER, /* long long int */ BENCODE_LIST, /* flat list of other objects */ BENCODE_DICTIONARY, /* dictionary of key/values pairs. keys are always strings */ + BENCODE_IOVEC, /* special case of a string, built through bencode_string_iovec() */ BENCODE_END_MARKER, /* used internally only */ }; @@ -115,6 +116,10 @@ static inline bencode_item_t *bencode_dictionary_add_string(bencode_item_t *dict /* Ditto, but for a "str" object */ static inline bencode_item_t *bencode_dictionary_add_str(bencode_item_t *dict, const char *key, const str *val); +/* XXX */ +static inline bencode_item_t *bencode_dictionary_add_iovec(bencode_item_t *dict, const char *key, + const struct iovec *iov, int iov_cnt, int str_len); + /* Ditto again, but adds the str object (val) to the bencode_buffer_t's internal free list. When * the bencode_item_t object is destroyed, BENCODE_FREE will be called on this pointer. */ static inline bencode_item_t *bencode_dictionary_add_str_free(bencode_item_t *dict, const char *key, str *val); @@ -135,6 +140,12 @@ bencode_item_t *bencode_list_add(bencode_item_t *list, bencode_item_t *item); /* Convenience function to add a string item to a list */ static inline bencode_item_t *bencode_list_add_string(bencode_item_t *list, const char *s); + + + + +/*** STRING BUILDING & HANDLING ***/ + /* Creates a new byte-string object. The given string does not have to be null-terminated, instead * the length of the string is specified by the "len" parameter. Returns NULL if no memory could * be allocated. @@ -142,24 +153,21 @@ static inline bencode_item_t *bencode_list_add_string(bencode_item_t *list, cons * the complete document is finally encoded or sent out. */ bencode_item_t *bencode_string_len(bencode_buffer_t *buf, const char *s, int len); - - - - -/*** STRING BUILDING & HANDLING ***/ - /* Creates a new byte-string object. The given string must be null-terminated. Otherwise identical * to bencode_string_len(). */ static inline bencode_item_t *bencode_string(bencode_buffer_t *buf, const char *s); -/* Convenience function to compare a string object to a regular C string. Returns 2 if object - * isn't a string object, otherwise returns according to strcmp(). */ -static inline int bencode_strcmp(bencode_item_t *a, const char *b); - /* Creates a new byte-string object from a "str" object. The string does not have to be null- * terminated. */ static inline bencode_item_t *bencode_str(bencode_buffer_t *buf, const str *s); +/* XXX */ +bencode_item_t *bencode_string_iovec(bencode_buffer_t *buf, const struct iovec *iov, int iov_cnt, int str_len); + +/* Convenience function to compare a string object to a regular C string. Returns 2 if object + * isn't a string object, otherwise returns according to strcmp(). */ +static inline int bencode_strcmp(bencode_item_t *a, const char *b); + /* Converts the string object "in" into a str object "out". Returns "out" on success, or NULL on * error ("in" was NULL or not a string object). */ static inline str *bencode_get_str(bencode_item_t *in, str *out); @@ -469,4 +477,10 @@ static inline void bencode_buffer_freelist_add(bencode_buffer_t *buf, void *p) { bencode_buffer_destroy_add(buf, BENCODE_FREE, p); } +static inline bencode_item_t *bencode_dictionary_add_iovec(bencode_item_t *dict, const char *key, + const struct iovec *iov, int iov_cnt, int str_len) +{ + return bencode_dictionary_add(dict, key, bencode_string_iovec(dict->buffer, iov, iov_cnt, str_len)); +} + #endif diff --git a/daemon/call.c b/daemon/call.c index 2a083c854..e84d5f94d 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -1473,47 +1473,57 @@ static void call_destroy(struct call *c) { -static int call_stream_address4(GString *o, struct peer *p, enum stream_address_format format) { +static int call_stream_address4(char *o, struct peer *p, enum stream_address_format format, int *len) { struct callstream *cs = p->up; u_int32_t ip4; struct callmaster *m = cs->call->callmaster; + int l = 0; - if (format == SAF_NG) - g_string_append(o, "IP4 "); + if (format == SAF_NG) { + strcpy(o + l, "IP4 "); + l = 4; + } ip4 = p->rtps[0].peer.ip46.s6_addr32[3]; - if (!ip4) - g_string_append(o, "0.0.0.0"); + if (!ip4) { + strcpy(o + l, "0.0.0.0"); + l += 7; + } else if (m->conf.adv_ipv4) - g_string_append_printf(o, IPF, IPP(m->conf.adv_ipv4)); + l += sprintf(o + l, IPF, IPP(m->conf.adv_ipv4)); else - g_string_append_printf(o, IPF, IPP(m->conf.ipv4)); + l += sprintf(o + l, IPF, IPP(m->conf.ipv4)); + *len = l; return AF_INET; } -static int call_stream_address6(GString *o, struct peer *p, enum stream_address_format format) { - char ips[64]; +static int call_stream_address6(char *o, struct peer *p, enum stream_address_format format, int *len) { struct callmaster *m = p->up->call->callmaster; + int l = 0; - if (format == SAF_NG) - g_string_append(o, "IP6 "); + if (format == SAF_NG) { + strcpy(o + l, "IP4 "); + l += 4; + } - if (IN6_IS_ADDR_UNSPECIFIED(&p->rtps[0].peer.ip46)) - g_string_append(o, "::"); + if (IN6_IS_ADDR_UNSPECIFIED(&p->rtps[0].peer.ip46)) { + strcpy(o + l, "::"); + l += 2; + } else { if (!IN6_IS_ADDR_UNSPECIFIED(&m->conf.adv_ipv6)) - inet_ntop(AF_INET6, &m->conf.adv_ipv6, ips, sizeof(ips)); + inet_ntop(AF_INET6, &m->conf.adv_ipv6, o + l, 45); /* lies... */ else - inet_ntop(AF_INET6, &m->conf.ipv6, ips, sizeof(ips)); - g_string_append(o, ips); + inet_ntop(AF_INET6, &m->conf.ipv6, o + l, 45); + l += strlen(o + l); } + *len = l; return AF_INET6; } - -int call_stream_address(GString *o, struct peer *p, enum stream_address_format format) { +int call_stream_address(char *o, struct peer *p, enum stream_address_format format, int *len) { struct callmaster *m; struct peer *other; @@ -1521,15 +1531,24 @@ int call_stream_address(GString *o, struct peer *p, enum stream_address_format f other = &p->up->peers[p->idx ^ 1]; if (other->desired_family == AF_INET) - return call_stream_address4(o, p, format); + return call_stream_address4(o, p, format, len); if (other->desired_family == 0 && IN6_IS_ADDR_V4MAPPED(&other->rtps[0].peer.ip46)) - return call_stream_address4(o, p, format); + return call_stream_address4(o, p, format, len); if (other->desired_family == 0 && IN6_IS_ADDR_UNSPECIFIED(&other->rtps[0].peer.ip46)) - return call_stream_address4(o, p, format); + return call_stream_address4(o, p, format, len); if (IN6_IS_ADDR_UNSPECIFIED(&m->conf.ipv6)) - return call_stream_address4(o, p, format); + return call_stream_address4(o, p, format, len); - return call_stream_address6(o, p, format); + return call_stream_address6(o, p, format, len); +} + +static int call_stream_address_gstring(GString *o, struct peer *p, enum stream_address_format format) { + int len, ret; + char buf[64]; /* 64 bytes ought to be enough for anybody */ + + ret = call_stream_address(buf, p, format, &len); + g_string_append_len(o, buf, len); + return ret; } @@ -1556,7 +1575,7 @@ static str *streams_print(GQueue *s, int num, enum call_opmode opmode, const cha t = s->head->data; if (format == SAF_TCP) - call_stream_address(o, &t->peers[off], format); + call_stream_address_gstring(o, &t->peers[off], format); for (i = 0, l = s->head; i < num && l; i++, l = l->next) { t = l->data; @@ -1565,7 +1584,7 @@ static str *streams_print(GQueue *s, int num, enum call_opmode opmode, const cha } if (format == SAF_UDP) { - af = call_stream_address(o, &t->peers[off], format); + af = call_stream_address_gstring(o, &t->peers[off], format); g_string_append_printf(o, " %c", (af == AF_INET) ? '4' : '6'); } @@ -2169,13 +2188,14 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, GQueue *streams, ben } static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster *m, bencode_item_t *output, enum call_opmode opmode, const char *tagname) { - str sdp, fromtag, viabranch, callid, *sdp_new; + str sdp, fromtag, viabranch, callid; char *errstr; GQueue parsed = G_QUEUE_INIT; GQueue streams = G_QUEUE_INIT; struct call *call; - int num; + int ret, num; struct sdp_ng_flags flags; + struct sdp_chopper *chopper; if (!bencode_dictionary_get_str(input, "sdp", &sdp)) return "No SDP body in message"; @@ -2201,17 +2221,20 @@ static const char *call_offer_answer_ng(bencode_item_t *input, struct callmaster goto out; log_info = &viabranch; + chopper = sdp_chopper_new(&sdp); + bencode_buffer_destroy_add(output->buffer, (free_func_t) sdp_chopper_destroy, chopper); num = call_streams(call, &streams, &fromtag, opmode); - sdp_new = sdp_replace(&sdp, &parsed, call, num, opmode, &flags); + ret = sdp_replace(chopper, &parsed, call, num, opmode, &flags); mutex_unlock(&call->lock); obj_put(call); errstr = "Error rewriting SDP"; - if (!sdp_new) + if (ret) goto out; - bencode_dictionary_add_str_free(output, "sdp", sdp_new); + bencode_dictionary_add_iovec(output, "sdp", &g_array_index(chopper->iov, struct iovec, 0), + chopper->iov_num, chopper->str_len); bencode_dictionary_add_string(output, "result", "ok"); errstr = NULL; diff --git a/daemon/call.h b/daemon/call.h index ce7f7ca7e..1155d9993 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -165,7 +165,7 @@ struct call *call_get_or_create(const str *callid, const str *viabranch, struct struct callstream *callstream_new(struct call *ca, int num); void callstream_init(struct callstream *s, struct relays_cache *); void kernelize(struct callstream *c); -int call_stream_address(GString *o, struct peer *p, enum stream_address_format); +int call_stream_address(char *o, struct peer *p, enum stream_address_format format, int *len); static inline char *call_strdup(struct call *c, const char *s) { char *r; diff --git a/daemon/control_ng.c b/daemon/control_ng.c index c852f320d..f93f0fcca 100644 --- a/daemon/control_ng.c +++ b/daemon/control_ng.c @@ -1,3 +1,5 @@ +#include +#include #include "control_ng.h" #include "obj.h" #include "poller.h" @@ -5,6 +7,7 @@ #include "log.h" #include "cookie_cache.h" #include "call.h" +#include "sdp.h" static void control_ng_incoming(struct obj *obj, str *buf, struct sockaddr_in6 *sin, char *addr) { diff --git a/daemon/sdp.c b/daemon/sdp.c index 7350b28a7..762c129fa 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -49,12 +49,6 @@ struct sdp_media { GQueue attributes; }; -struct string_chopper { - str *input; - GString *output; - int position; -}; - @@ -347,13 +341,42 @@ error: return -1; } -static void chopper_init(struct string_chopper *c, str *input) { +struct sdp_chopper *sdp_chopper_new(str *input) { + struct sdp_chopper *c = g_slice_alloc0(sizeof(*c)); c->input = input; - c->output = g_string_new_str(); - c->position = 0; + c->chunk = g_string_chunk_new(512); + c->iov = g_array_new(0, 0, sizeof(struct iovec)); + return c; +} + +static void chopper_append(struct sdp_chopper *c, const char *s, int len) { + struct iovec *iov; + + g_array_set_size(c->iov, ++c->iov_num); + iov = &g_array_index(c->iov, struct iovec, c->iov_num - 1); + iov->iov_base = (void *) s; + iov->iov_len = len; + c->str_len += len; +} + +static void chopper_append_dup(struct sdp_chopper *c, const char *s, int len) { + return chopper_append(c, g_string_chunk_insert_len(c->chunk, s, len), len); +} + +static void chopper_append_printf(struct sdp_chopper *c, const char *fmt, ...) __attribute__((format(printf,2,3))); + +static void chopper_append_printf(struct sdp_chopper *c, const char *fmt, ...) { + char buf[32]; + int l; + va_list va; + + va_start(va, fmt); + l = vsnprintf(buf, sizeof(buf) - 1, fmt, va); + va_end(va); + chopper_append(c, g_string_chunk_insert_len(c->chunk, buf, l), l); } -static int copy_up_to(struct string_chopper *chop, str *where) { +static int copy_up_to(struct sdp_chopper *chop, str *where) { int offset, len; offset = where->s - chop->input->s; @@ -365,20 +388,20 @@ static int copy_up_to(struct string_chopper *chop, str *where) { mylog(LOG_WARNING, "Malformed SDP, cannot rewrite"); return -1; } - g_string_append_len(chop->output, chop->input->s + chop->position, len); + chopper_append(chop, chop->input->s + chop->position, len); chop->position += len; return 0; } -static void copy_remainder(struct string_chopper *chop) { +static void copy_remainder(struct sdp_chopper *chop) { int len; len = chop->input->len - chop->position; assert(len >= 0); - g_string_append_len(chop->output, chop->input->s + chop->position, len); + chopper_append(chop, chop->input->s + chop->position, len); chop->position += len; } -static int skip_over(struct string_chopper *chop, str *where) { +static int skip_over(struct sdp_chopper *chop, str *where) { int offset, len; offset = (where->s - chop->input->s) + where->len; @@ -394,7 +417,7 @@ static int skip_over(struct string_chopper *chop, str *where) { return 0; } -static int replace_media_port(struct string_chopper *chop, struct sdp_media *media, GList *m, int off) { +static int replace_media_port(struct sdp_chopper *chop, struct sdp_media *media, GList *m, int off) { struct callstream *cs; struct streamrelay *sr; str *port = &media->port; @@ -411,7 +434,7 @@ static int replace_media_port(struct string_chopper *chop, struct sdp_media *med if (copy_up_to(chop, port)) return -1; - g_string_append_printf(chop->output, "%hu", sr->fd.localport); + chopper_append_printf(chop, "%hu", sr->fd.localport); if (skip_over(chop, port)) return -1; @@ -431,14 +454,16 @@ warn: } } - g_string_append_printf(chop->output, "/%i", cons); + chopper_append_printf(chop, "/%i", cons); return cons; } -static int replace_network_address(struct string_chopper *chop, struct network_address *address, GList *m, int off, struct sdp_ng_flags *flags) { +static int replace_network_address(struct sdp_chopper *chop, struct network_address *address, GList *m, int off, struct sdp_ng_flags *flags) { struct callstream *cs; struct peer *peer; + char buf[64]; + int len; if (!m) { mylog(LOG_ERROR, "BUG! Ran out of streams"); @@ -452,12 +477,14 @@ static int replace_network_address(struct string_chopper *chop, struct network_a return -1; if (!flags->trust_address && flags->received_from_family.len == 3 && flags->received_from_address.len) { - g_string_append_len(chop->output, flags->received_from_family.s, flags->received_from_family.len); - g_string_append_c(chop->output, ' '); - g_string_append_len(chop->output, flags->received_from_address.s, flags->received_from_address.len); + chopper_append(chop, flags->received_from_family.s, flags->received_from_family.len); + chopper_append(chop, " ", 1); + chopper_append(chop, flags->received_from_address.s, flags->received_from_address.len); + } + else { + call_stream_address(buf, peer, SAF_NG, &len); + chopper_append_dup(chop, buf, len); } - else - call_stream_address(chop->output, peer, SAF_NG); if (skip_over(chop, &address->address)) return -1; @@ -465,25 +492,21 @@ static int replace_network_address(struct string_chopper *chop, struct network_a return 0; } -static str *chopper_done(struct string_chopper *chop) { - str *ret; - ret = g_string_free_str(chop->output); - return ret; -} - -static void chopper_destroy(struct string_chopper *chop) { - g_string_free(chop->output, TRUE); +void sdp_chopper_destroy(struct sdp_chopper *chop) { + g_string_chunk_free(chop->chunk); + g_array_free(chop->iov, 1); + g_slice_free1(sizeof(*chop), chop); } /* XXX use stream numbers as index */ /* XXX use port numbers as index */ /* XXX get rid of num/off parameters? */ -/* XXX use iovec based rewriting */ -str *sdp_replace(str *body, GQueue *sessions, struct call *call, int num, enum call_opmode opmode, struct sdp_ng_flags *flags) { +int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call *call, int num, + enum call_opmode opmode, struct sdp_ng_flags *flags) +{ struct sdp_session *session; struct sdp_media *media; GList *l, *k, *m; - struct string_chopper chop; int off, skip; off = opmode; @@ -491,30 +514,29 @@ str *sdp_replace(str *body, GQueue *sessions, struct call *call, int num, enum c off ^= 1; num = abs(num); - chopper_init(&chop, body); m = call->callstreams->head; for (l = sessions->head; l; l = l->next) { session = l->data; if (session->origin.parsed && flags->replace_origin) { - if (replace_network_address(&chop, &session->origin.address, m, off, flags)) + if (replace_network_address(chop, &session->origin.address, m, off, flags)) goto error; } if (session->connection.parsed) { - if (replace_network_address(&chop, &session->connection.address, m, off, flags)) + if (replace_network_address(chop, &session->connection.address, m, off, flags)) goto error; } for (k = session->media_streams.head; k; k = k->next) { media = k->data; - skip = replace_media_port(&chop, media, m, off); + skip = replace_media_port(chop, media, m, off); if (skip < 0) goto error; if (media->connection.parsed && flags->replace_sess_conn) { - if (replace_network_address(&chop, &media->connection.address, m, off, flags)) + if (replace_network_address(chop, &media->connection.address, m, off, flags)) goto error; } @@ -522,11 +544,10 @@ str *sdp_replace(str *body, GQueue *sessions, struct call *call, int num, enum c } } - copy_remainder(&chop); - return chopper_done(&chop); + copy_remainder(chop); + return 0; error: mylog(LOG_ERROR, "Error rewriting SDP"); - chopper_destroy(&chop); - return NULL; + return -1; } diff --git a/daemon/sdp.h b/daemon/sdp.h index 448eb40ad..5b69889aa 100644 --- a/daemon/sdp.h +++ b/daemon/sdp.h @@ -17,9 +17,21 @@ struct sdp_ng_flags { replace_sess_conn:1; }; +struct sdp_chopper { + str *input; + int position; + GStringChunk *chunk; + GArray *iov; + int iov_num; + int str_len; +}; + int sdp_parse(str *body, GQueue *sessions); int sdp_streams(const GQueue *sessions, GQueue *streams); void sdp_free(GQueue *sessions); -str *sdp_replace(str *body, GQueue *sessions, struct call *call, int num, enum call_opmode, struct sdp_ng_flags *); +int sdp_replace(struct sdp_chopper *, GQueue *, struct call *, int, enum call_opmode, struct sdp_ng_flags *); + +struct sdp_chopper *sdp_chopper_new(str *input); +void sdp_chopper_destroy(struct sdp_chopper *chop); #endif