2

I'm completely stuck with this for about 2 days now and it seems I simply can't get my head around the problem I'm facing.

Currently, I'm writing an SDP parsing library that should also be usable for creating correct SDP messages according to it's specification (https://www.rfc-editor.org/rfc/rfc4566). But the specification is sometimes very open or unclear, so I try to implement a necessary amount of flexibility while still being as close to the RFC as possible.

Example Problem

A SDP message can contain media information ("m" field) where as this information has the following pattern:

m=<media> <port> <proto> <fmt> ...

And example message could look like this:

m=video 49170/2 RTP/AVP 31

Take a look at the proto flag, which stands for Media Transport Protocol. According to the specification, this field can have the following values:

  • RTP/AVP
  • RTP/SAVP
  • UDP

This is a list of values, so it's obviously appropriate to take an enumeration.

public enum MediaTransportProtocolType {
    RTP/AVP
    RTP/SAVP
    UDP
}

Ooops! But this doesn't work because of the "/" char. So, how am I able to use this for parsing? I extended the Enumeration fields with the DescriptionAttribute

public enum MediaTransportProtocolType {
    [Description("RTP/AVP")
    RTP_AVP
    [Description("RTP/SAVP")
    RTP_SAVP
    [Description("UDP")
    UDP
}

Now I can simply look up the appropriate media transport protocol type by it's description. But now, the RFC specification goes on:

This memo registers three values [...] If other RTP profiles are 
defined in the future [...]

So it's possible that a future network device can send me an media transport protocol that I'm not aware of. The whole enumeration thing doesn't work here anymore as System.Enum is not extendable due to various reason.

Solution

On my way looking for a solution I met the Type Safe Enumeration Pattern (AKA StringEnum) as described in here: how can i use switch statement on type-safe enum pattern. This answer even describes a solution to make them switchable, even if it's an ugly solution IMHO.

But again, this does only work for a defined range. I extended the Type Safe Enumeration class with a dictionary to store instances that I can look up while parsing, but also add new ones if I don't now them.

But what about all the other fields? And what about casting?

This answer here describes an approach with a base class and casting through explicit operators: Casting string to type-safe-enum using user-defined conversion

I tried it out, but it's not working the way I'd like it to be. (Invalid casting exceptions, dirty two casts pattern, not extendable).

How does one parse SDP information correctly and easily while still providing a library that allows to create a correct SDP?

Community
  • 1
  • 1
Atrotygma
  • 1,133
  • 3
  • 17
  • 31
  • If I understand your requirements, your library must have an "extendable behavior", that means: if in the future more protocol types will be added, different behavior must happen if some of these protocol types are found. So, I would think about a pluggable architecture: try to define interfaces where extensibility is needed; then, for each implementation of the interface, create a different assembly; at runtime you're going to load all the assemblies and, by a configuration file, you're going to map interfaces to classes that implement the desired behavior. Check this out unity.codeplex.com – br1 Jul 18 '13 at 09:11
  • @br1 That's an utter wild goose chase. The idea isn't to extend *behaviour* but just to allow new values in an enum. A plugin architecture is overkill here. – millimoose Jul 18 '13 at 09:27
  • Only @Atrotygma can confirm this, but I suppose that somewhere in his code he's going to "switch" the behavior of his library based upon the values of an enum variable. – br1 Jul 18 '13 at 09:30
  • 1
    @br Confirmed. A plugin architecture for a simple parser seems like a dog digging a 10 feet hole for hiding his bone: He will loose himself eventually due to unnecessary depth. I use switch statements not because of behavior, but to store objects where they belong. – Atrotygma Jul 18 '13 at 09:48
  • @br1 Not necessarily if this is a pure parser library and the idea is to design this to be flexible yet convenient to use for actual applications processing SDP, not to tie it to behaviour up front. Asking applications to provide plugins / handlers for your parser is a simply ludicrous API surface for parsing. (Think SAX.) – millimoose Jul 18 '13 at 10:14

1 Answers1

1

You can do something like this:

public sealed class MediaTransportProtocolType
{
    public static readonly MediaTransportProtocolType RtpAvp =
        new MediaTransportProtocolType("RTP/AVP");

    public static readonly MediaTransportProtocolType RtpSavp =
        new MediaTransportProtocolType("RTP/SAVP");

        public static readonly MediaTransportProtocolType Udp =
        new MediaTransportProtocolType("UDP");

    public static readonly ReadOnlyCollection<MediaTransportProtocolType>
        Values = new ReadOnlyCollection<MediaTransportProtocolType>(
            new MediaTransportProtocolType[] { RtpAvp, RtpSavp, Udp });

    private MediaTransportProtocolType(string name)
    {
        this.Name = name;
    }

    public string Name { get; private set; }

    public static MediaTransportProtocolType Parse(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            throw new ArgumentNullException("value");
        }

        var comparer = StringComparer.OrdinalIgnoreCase;

        if (comparer.Equals(value, RtpAvp.Name))
        {
            return RtpAvp;
        }
        else if (comparer.Equals(value, RtpSavp.Name))
        {
            return RtpSavp;
        }
        else if (comparer.Equals(value, Udp.Name))
        {
            return Udp;
        }
        else if (value.StartsWith("RTP/", StringComparison.OrdinalIgnoreCase))
        {
            // Normally we would throw an exception here, but  future
            // protocols are expected and we must be forward compatible.
            return new MediaTransportProtocolType(name);
        }

        throw new FormatException(
            "The value is not in an expected format. Value: " + value);
    }
}

That allows you to use it as an enum like this:

var type = MediaTransportProtocolType.Udp;

And you can parse it:

var type = MediaTransportProtocolType.Parse(value);

And iterate over all known values:

foreach (var type in MediaTransportProtocolType.Values)
{
}

And parse returns a unknown/future protocol type as long as they start with "RTP/" (as defined by the specs).

Of course the question is, can your library handle 'unknown' protocols. If you can't, you shouldn't allow parsing them. You should throw an NotSupportedException and update the library when new protocols are added. Or if you want others to extend, you should allow others to define implementations that handle a specific protocol.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • Yes, this is exactly what went through my head and I see this is the only way to implement it properly. But it would be nicer to have something like a base class for this kind of "enum" because there are about 10 such fields and I don't intend to repeat nearly exactly the same thing over and over again for each. And that's where I'm stuck. :( – Atrotygma Jul 18 '13 at 09:26
  • @Atrotygma: see my little update of the parse method. I added some constraints. – Steven Jul 18 '13 at 09:30
  • Well, again: This forces me to write nearly the same code for every other possible field in the SDP, such as a NetworkType, AddressType, MediaType, AttributeType... – Atrotygma Jul 18 '13 at 09:55