All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xpertss.sdp.SessionParser Maven / Gradle / Ivy

/**
 * Created By: cfloersch
 * Date: 6/2/13
 * Copyright 2013 XpertSoftware
 */
package xpertss.sdp;

import xpertss.lang.Integers;
import xpertss.lang.Longs;
import xpertss.lang.Objects;
import xpertss.lang.Strings;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;

import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;

/**
 * Parser to parse SessionDescription objects from standard SDP files.
 * 

* This parser performs only rudimentary validation of data. Numbers * must properly parse to numbers, the minimum number of fields must * be defined, and field ordering is enforced. However, relational * rules are not currently implemented. *

 *   Session description
 *     v=  (one protocol version)
 *     o=  (one originator and session identifier)
 *     s=  (one session name)
 *     i=  (zero or one session information)
 *     u=  (zero or one URI of description)
 *     e=  (zero or more email address)
 *     p=  (zero or more phone number)
 *     c=  (zero or one connection information)
 *     b=  (zero or more bandwidth information lines)
 *     One or more time descriptions ("t=" and "r=" lines; see below)
 *     z=  (zero or one time zone adjustments)
 *     k=  (zero or one encryption key)
 *     a=  (zero or more session attribute lines)
 *     Zero or more media descriptions
 *
 *   Time description
 *     t=  (one time the session is active)
 *     r=  (zero or more repeat times)
 *
 *   Media description, if present
 *     m=  (one media name and transport address)
 *     i=  (zero or one media title)
 *     c=  (zero or one connection information)
 *     b=  (zero or more bandwidth information lines)
 *     k=  (zero or one encryption key)
 *     a=  (zero or more media attribute lines)
 * 
*/ public class SessionParser { private static final Set validchars = new HashSet<>(); static { validchars.add("v"); validchars.add("o"); validchars.add("s"); validchars.add("i"); validchars.add("u"); validchars.add("e"); validchars.add("p"); validchars.add("c"); validchars.add("b"); validchars.add("t"); validchars.add("r"); validchars.add("z"); validchars.add("k"); validchars.add("a"); validchars.add("m"); } /** * Parse a string which represents an SDP file returning a Session Description if * it successfully parsed the data. * * @throws SdpParseException If an error occurs parsing the structure of the document * @throws NullPointerException If the supplied source string is null */ public SessionDescription parse(String str) throws SdpParseException, NullPointerException { return parse(new Scanner(str)); } /** * Parse the contents of a given sdp file identified by a Path object returning a Session * Description if it successfully parsed the data. * * @throws SdpParseException If an error occurs parsing the structure of the document * @throws NullPointerException If the supplied source path is null * @throws IOException If an I/O error occurs opening source */ public SessionDescription parse(Path path) throws SdpParseException, NullPointerException, IOException { return parse(new Scanner(path, "UTF-8")); } /** * Parse the sdp contents from a given Reader object returning a Session Description if it * successfully parsed the data. * * @throws SdpParseException If an error occurs parsing the structure of the document * @throws NullPointerException If the supplied source reader is null */ public SessionDescription parse(Reader reader) throws SdpParseException, NullPointerException { return parse(new Scanner(reader)); } /** * Parse the sdp contents from a given InputStream object returning a Session Description if it * successfully parsed the data. * * @throws SdpParseException If an error occurs parsing the structure of the document * @throws NullPointerException If the supplied source stream is null */ public SessionDescription parse(InputStream stream) throws SdpParseException, NullPointerException { return parse(new Scanner(stream, "UTF-8")); } private SessionDescription parse(Scanner scanner) throws SdpParseException { try { SessionBuilder builder = SessionBuilder.create(); SessionFieldParser chain = createParserChain(); while(scanner.hasNextLine()) { String line = scanner.nextLine(); if(Strings.isEmpty(line)) break; if(line.length() < 3 || line.charAt(1) != '=') throw new SdpParseException("invalid line format: " + line); if(!validchars.contains(Character.toString(line.charAt(0)))) throw new SdpParseException("invalid field: " + line); chain = chain.parse(builder, line); } chain.finish(builder); return builder.build(); } finally { scanner.close(); } } private SessionFieldParser createParserChain() { SessionFieldParser parser = new MediaFieldParser(); parser = new KeyFieldParser(new AttributeFieldParser(parser)); parser = new TimeFieldParser(new TimeZonesFieldParser(parser)); parser = new ConnectionFieldParser(new BandwidthFieldParser(parser)); parser = new EmailFieldParser(new PhoneFieldParser(parser)); parser = new InfoFieldParser(new UriFieldParser(parser)); parser = new OriginFieldParser(new SessionNameFieldParser(parser)); return new VersionFieldParser((parser)); } private interface SessionFieldParser { public SessionFieldParser parse(SessionBuilder builder, String line); public void finish(SessionBuilder builder); } private interface MediaSubFieldParser { public MediaSubFieldParser parse(MediaBuilder builder, String line); public void finish(MediaBuilder builder); } private class VersionFieldParser implements SessionFieldParser { private SessionFieldParser next; public VersionFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("v")) { int version = Integers.parse(line.substring(2), -1); if(version < 0) throw new SdpParseException("invalid version specified: " + line.substring(2)); builder.setVersion(version); return next; } throw new SdpParseException("invalid session description: expecting version"); } @Override public void finish(SessionBuilder builder) { throw new SdpParseException("premature end of stream: expecting version"); } } private class OriginFieldParser implements SessionFieldParser { private SessionFieldParser next; public OriginFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("o")) { String[] parts = line.substring(2).split("\\s+"); if(parts.length != 6) throw new SdpParseException("invalid origin line: " + line.substring(2)); OriginBuilder origin = OriginBuilder.create().setUsername(parts[0]); long sessionId = Longs.parse(parts[1], -1); if(sessionId < 0) throw new SdpParseException("invalid origin sessionId: " + parts[1]); origin.setSessionId(sessionId); long version = Longs.parse(parts[2], -1); if(version < 0) throw new SdpParseException("invalid origin session version: " + parts[2]); origin.setSessionVersion(version); origin.setNetworkType(parts[3]).setAddressType(parts[4]).setAddress(parts[5]); builder.setOrigin(origin.build()); return next; } throw new SdpParseException("invalid session description: expecting origin"); } @Override public void finish(SessionBuilder builder) { throw new SdpParseException("premature end of stream: expecting origin"); } } private class SessionNameFieldParser implements SessionFieldParser { private SessionFieldParser next; public SessionNameFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("s")) { builder.setSessionName(line.substring(2)); return next; } throw new SdpParseException("invalid session description: expecting session name"); } @Override public void finish(SessionBuilder builder) { throw new SdpParseException("premature end of stream: expecting session name"); } } private class InfoFieldParser implements SessionFieldParser { private SessionFieldParser next; public InfoFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("i")) { builder.setInfo(line.substring(2)); return next; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class UriFieldParser implements SessionFieldParser { private SessionFieldParser next; public UriFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("u")) { builder.setUri(line.substring(2)); return next; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class EmailFieldParser implements SessionFieldParser { private SessionFieldParser next; public EmailFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("e")) { builder.addEmail(line.substring(2)); return this; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class PhoneFieldParser implements SessionFieldParser { private SessionFieldParser next; public PhoneFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("p")) { builder.addPhone(line.substring(2)); return this; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class ConnectionFieldParser implements SessionFieldParser { private SessionFieldParser next; public ConnectionFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("c")) { String[] parts = line.substring(2).split("\\s+"); if(parts.length != 3) throw new SdpParseException("invalid connection line: " + line.substring(2)); builder.setConnection(parts[2], parts[1], parts[0]); return next; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class BandwidthFieldParser implements SessionFieldParser { private SessionFieldParser next; public BandwidthFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("b")) { String[] parts = line.substring(2).split(":"); if(parts.length != 2) throw new SdpParseException("invalid bandwidth line: " + line.substring(2)); int kbps = Integers.parse(parts[1], -1); if(kbps < 0) throw new SdpParseException("invalid bandwidth value specified: " + parts[1]); builder.addBandwidth(parts[0], kbps); return this; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class TimeFieldParser implements SessionFieldParser { private SessionFieldParser next; private TimeBuilder time; public TimeFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("t")) { if(time != null) builder.addTimeDescription(time.build()); String[] parts = line.substring(2).split("\\s+"); if(parts.length != 2) throw new SdpParseException("invalid time field: " + line.substring(2)); long start = Longs.parse(parts[0], -1); if(start < 0) throw new SdpParseException("invalid start time: " + parts[0]); long stop = Longs.parse(parts[1], -1); if(stop < 0) throw new SdpParseException("invalid stop time: " + parts[1]); time = TimeBuilder.create().setTime(Utils.toDate(start), Utils.toDate(stop)); return this; } else if(line.startsWith("r")) { if(time == null) throw new SdpParseException("invalid session description: expecting time"); String[] parts = line.substring(2).split("\\s+"); if(parts.length < 3) throw new SdpParseException("invalid repeat time field: " + line.substring(2)); long interval = fromCompactTime(parts[0]); if(interval < 1) throw new SdpParseException("invalid repeat time interval: " + parts[0]); long duration = fromCompactTime(parts[1]); if(duration < 1) throw new SdpParseException("invalid repeat time duration: " + parts[1]); long[] offsets = new long[parts.length - 2]; for(int i = 0; i < offsets.length; i++) { offsets[i] = fromCompactTime(parts[i+2]); if(offsets[i] < 0) throw new SdpParseException("invalid repeat time offset: " + parts[i+2]); } time.addRepeatTime(interval, duration, offsets); return this; } else if(time != null) { builder.addTimeDescription(time.build()); return next.parse(builder, line); } throw new SdpParseException("invalid session description: expecting time"); } @Override public void finish(SessionBuilder builder) { if(time != null) builder.addTimeDescription(time.build()); else throw new SdpParseException("premature end of stream: expecting time"); } } private class TimeZonesFieldParser implements SessionFieldParser { private SessionFieldParser next; public TimeZonesFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("z")) { String[] parts = line.substring(2).split("\\s+"); if(parts.length % 2 != 0) throw new SdpParseException("invalid timezones field: " + line.substring(2)); for(int i = 0; i < parts.length - 1; i += 2) { long date = Longs.parse(parts[i], -1); if(date < 0) throw new SdpParseException("invalid date found: " + parts[i]); builder.addTimeAdjustment(Utils.toDate(date), fromCompactTime(parts[i + 1])); } return next; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class KeyFieldParser implements SessionFieldParser { private SessionFieldParser next; public KeyFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("k")) { int idx = line.indexOf(":"); if(idx < 0) { builder.setKey(line.substring(2), null); } else if(idx < line.length() - 1) { builder.setKey(line.substring(2, idx), line.substring(idx + 1)); } else { builder.setKey(line.substring(2, idx), null); } return next; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class AttributeFieldParser implements SessionFieldParser { private SessionFieldParser next; public AttributeFieldParser(SessionFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("a")) { int idx = line.indexOf(":"); if(idx < 0) { builder.addAttribute(line.substring(2), null); } else if(idx == line.length() - 1) { builder.addAttribute(line.substring(2, idx), null); } else { builder.addAttribute(line.substring(2, idx), line.substring(idx+1)); } return this; } return next.parse(builder, line); } @Override public void finish(SessionBuilder builder) { next.finish(builder); } } private class MediaFieldParser implements SessionFieldParser { private MediaBuilder media; private MediaSubFieldParser chain; @Override public SessionFieldParser parse(SessionBuilder builder, String line) { if(line.startsWith("m")) { if(media != null) { builder.addMediaDescription(media.build()); } media = MediaBuilder.create(); chain = createParserChain(); String[] parts = line.substring(2).split("\\s+"); if(parts.length < 4) throw new SdpParseException("incomplete media field: " + line.substring(2)); String[] ports = parts[1].split("/", 2); int port = Integers.parse(ports[0], -1); if(port < 0 || port > 65535) throw new SdpParseException("found invalid port: " + ports[0]); int count = (ports.length < 2) ? 1 : Integers.parse(ports[1], -1); if(count < 1) throw new SdpParseException("found invalid port count: " + ports[1]); int[] formats = new int[parts.length - 3]; for(int i = 0; i < formats.length; i++) { formats[i] = Integers.parse(parts[i+3], -1); if(formats[i] < 0) throw new SdpParseException("invalid format found: " + parts[i+3]); } media.setMedia(parts[0], port, count, parts[2], formats); } else if(media != null) { chain = chain.parse(media, line); } else { throw new SdpParseException("invalid session description: expecting media"); } return this; } @Override public void finish(SessionBuilder builder) { if(media != null) { chain.finish(media); builder.addMediaDescription(media.build()); } } private MediaSubFieldParser createParserChain() { MediaSubFieldParser parser = new AttributeMediaSubFieldParser(); parser = new KeyMediaSubFieldParser(parser); parser = new BandwidthMediaSubFieldParser(parser); parser = new ConnectionMediaSubFieldParser(parser); return new InfoMediaSubFieldParser(parser); } private class InfoMediaSubFieldParser implements MediaSubFieldParser { private MediaSubFieldParser next; public InfoMediaSubFieldParser(MediaSubFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public MediaSubFieldParser parse(MediaBuilder builder, String line) { if(line.startsWith("i")) { builder.setInfo(line.substring(2)); return next; } return next.parse(builder, line); } @Override public void finish(MediaBuilder builder) { next.finish(builder); } } private class ConnectionMediaSubFieldParser implements MediaSubFieldParser { private MediaSubFieldParser next; public ConnectionMediaSubFieldParser(MediaSubFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public MediaSubFieldParser parse(MediaBuilder builder, String line) { if(line.startsWith("c")) { String[] parts = line.substring(2).split("\\s+"); if(parts.length != 3) throw new SdpParseException("invalid connection line: " + line.substring(2)); builder.setConnection(parts[2], parts[1], parts[0]); return next; } return next.parse(builder, line); } @Override public void finish(MediaBuilder builder) { next.finish(builder); } } private class BandwidthMediaSubFieldParser implements MediaSubFieldParser { private MediaSubFieldParser next; public BandwidthMediaSubFieldParser(MediaSubFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public MediaSubFieldParser parse(MediaBuilder builder, String line) { if(line.startsWith("b")) { String[] parts = line.substring(2).split(":"); if(parts.length != 2) throw new SdpParseException("invalid bandwidth line: " + line.substring(2)); int kbps = Integers.parse(parts[1], -1); if(kbps < 0) throw new SdpParseException("invalid bandwidth value specified: " + parts[1]); builder.addBandwidth(parts[0], kbps); return this; } return next.parse(builder, line); } @Override public void finish(MediaBuilder builder) { next.finish(builder); } } private class KeyMediaSubFieldParser implements MediaSubFieldParser { private MediaSubFieldParser next; public KeyMediaSubFieldParser(MediaSubFieldParser next) { this.next = Objects.notNull(next, "next may not be null"); } @Override public MediaSubFieldParser parse(MediaBuilder builder, String line) { if(line.startsWith("k")) { int idx = line.indexOf(":"); if(idx < 0) { builder.setKey(line.substring(2), null); } else if(idx < line.length() - 1) { builder.setKey(line.substring(2, idx), line.substring(idx + 1)); } else { builder.setKey(line.substring(2, idx), null); } return next; } return next.parse(builder, line); } @Override public void finish(MediaBuilder builder) { next.finish(builder); } } private class AttributeMediaSubFieldParser implements MediaSubFieldParser { @Override public MediaSubFieldParser parse(MediaBuilder builder, String line) { if(line.startsWith("a")) { int idx = line.indexOf(":"); if(idx < 0) { builder.addAttribute(line.substring(2), null); } else if(idx == line.length() - 1) { builder.addAttribute(line.substring(2, idx), null); } else { builder.addAttribute(line.substring(2, idx), line.substring(idx+1)); } return this; } throw new SdpParseException("misplaced field: " + line); } @Override public void finish(MediaBuilder builder) { // what to do?? } } } private static long fromCompactTime(String compact) { try { char lastChar = compact.charAt(compact.length() - 1); if(!Character.isDigit(lastChar)) { long value = Long.parseLong(compact.substring(0, compact.length() - 1)); if(lastChar == 'd') { return DAYS.toSeconds(value); } else if(lastChar == 'h') { return HOURS.toSeconds(value); } else if(lastChar == 'm') { return MINUTES.toSeconds(value); } else if(lastChar == 's') { return SECONDS.toSeconds(value); } throw new SdpParseException("unknown time unit found: " + compact); } else { return Long.parseLong(compact); } } catch(NumberFormatException nfe) { throw new SdpParseException("invalid time unit found: " + compact); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy