public class Headers {

   public enum Type {
      Http, Rtsp, Mail
   }


   private List<Header> headers;
   private boolean readOnly;

   public Headers(Type type)
   {
      headers = new ArrayList<Header>();

      if (Type.Mail == type) {
         headers.add(new PositionHeader("Return-Path"));
         headers.add(new PositionHeader("Received"));
         headers.add(new PositionHeader("Message-ID"));
         headers.add(new PositionHeader("Resent-Date"));
         headers.add(new PositionHeader("Date"));
         headers.add(new PositionHeader("Resent-From"));
         headers.add(new PositionHeader("From"));
         headers.add(new PositionHeader("Sender"));
         headers.add(new PositionHeader("Reply-To"));
         headers.add(new PositionHeader("To"));
         headers.add(new PositionHeader("Subject"));
         headers.add(new PositionHeader("Cc"));
         headers.add(new PositionHeader("In-Reply-To"));
         headers.add(new PositionHeader("Resent-Message-ID"));
         headers.add(new PositionHeader("Errors-To"));
         headers.add(new PositionHeader("Mime-Version"));
         headers.add(new PositionHeader("Content-Type"));
         headers.add(new PositionHeader("Content-Transfer-Encoding"));
         headers.add(new PositionHeader("Content-MD5"));
         headers.add(new PositionHeader(":"));
         headers.add(new PositionHeader("Content-Length"));
         headers.add(new PositionHeader("Status"));
      } else if(type == Type.Http) {
         headers.add(new PositionHeader("Host"));
         headers.add(new PositionHeader("User-Agent"));
         headers.add(new PositionHeader("Date"));

         headers.add(new PositionHeader("Accept"));
         headers.add(new PositionHeader("Accept-Language"));
         headers.add(new PositionHeader("Accept-Encoding"));
         headers.add(new PositionHeader("Accept-Charset"));

         headers.add(new PositionHeader("Authorization"));
         headers.add(new PositionHeader("Proxy-Authorization"));

         headers.add(new PositionHeader("Referer"));
         headers.add(new PositionHeader("If-Modified-Since"));
         headers.add(new PositionHeader("If-None-Match"));

         headers.add(new PositionHeader("Transfer-Encoding"));

         headers.add(new PositionHeader("Content-Disposition"));
         headers.add(new PositionHeader("Content-Type"));
         headers.add(new PositionHeader("Content-Location"));
         headers.add(new PositionHeader("Content-MD5"));
         headers.add(new PositionHeader("Content-Language"));
         headers.add(new PositionHeader("Content-Encoding"));
         headers.add(new PositionHeader("Content-Length"));
         headers.add(new PositionHeader(":"));

         headers.add(new PositionHeader("Proxy-Connection"));
         headers.add(new PositionHeader("Connection"));
         headers.add(new PositionHeader("Pragma"));
         headers.add(new PositionHeader("Cache-Control"));
      } else if(type == Type.Rtsp) {
         headers.add(new PositionHeader("Server"));
         headers.add(new PositionHeader("CSeq"));
         headers.add(new PositionHeader("User-Agent"));
         headers.add(new PositionHeader("Date"));
         headers.add(new PositionHeader("Expires"));

         headers.add(new PositionHeader("Accept"));
         headers.add(new PositionHeader("Accept-Language"));
         headers.add(new PositionHeader("Accept-Encoding"));

         headers.add(new PositionHeader("Authorization"));

         headers.add(new PositionHeader("Referer"));
         headers.add(new PositionHeader("If-Modified-Since"));
         headers.add(new PositionHeader("If-Match"));
         headers.add(new PositionHeader("Last-Modified"));

         headers.add(new PositionHeader("Transport"));
         headers.add(new PositionHeader("Session"));
         headers.add(new PositionHeader("RTP-Info"));
         headers.add(new PositionHeader("Location"));
         headers.add(new PositionHeader("Range"));


         headers.add(new PositionHeader("Content-Base"));
         headers.add(new PositionHeader("Content-Type"));
         headers.add(new PositionHeader("Content-Location"));
         headers.add(new PositionHeader("Content-Language"));
         headers.add(new PositionHeader("Content-Encoding"));
         headers.add(new PositionHeader("Content-Length"));
         headers.add(new PositionHeader(":"));

         headers.add(new PositionHeader("Connection"));
         headers.add(new PositionHeader("Cache-Control"));

      }
   }

   private Headers(List<Header> headers)
   {
      this.headers = Objects.notNull(headers);
      this.readOnly = true;
   }


   /**
    * Return an enumeration of the headers in their predefined order.
    */
   public Enumeration<Header> headers()
   {
      Predicate<Object> filter = Predicates.instanceOf(PositionHeader.class);
      return new FilteringEnumeration(headers, filter);
   }

   public boolean contains(String name)
   {
      return getHeader(name) != null;
   }


   /**
    * This will return the first header with the given name.
    */
   public Header getHeader(String name)
   {
      for(Header header : headers) {
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            if(!(header instanceof PositionHeader)) return header;
         }
      }
      return null;
   }

   /**
    * This will return all the header with the given name in the order they
    * were found.
    */
   public Header[] getHeaders(String name)
   {
      List<Header> results = new ArrayList<Header>();
      for(Header header : headers) {
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            if(!(header instanceof PositionHeader)) results.add(header);
         }
      }
      return results.toArray(new Header[results.size()]);
   }


   /**
    * This will add the named header to the end of the set of existing
    * headers with the same name or its default position within the set
    * if no header with the same name pre-exists.
    *
    * @throws MalformedException If the header name or value are malformed
    */
   public void addHeader(String name, String value) throws MalformedException
   {
      if(readOnly) throw new IllegalStateException("src are readonly");
      Header newHeader = HeaderParser.parse(name, value);
      int pos = 0;
      for(int i = headers.size() - 1; i >= 0; i--) {
         Header header = headers.get(i);
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            headers.add(i + 1, newHeader);
            return;
         } else if(header.getName().equals(":")) {
            pos = i;
         }
      }
      headers.add(pos + 1, newHeader);
   }

   /**
    * This will add the named header to its default position within the set
    * removing any previously existing headers with the same name.
    *
    * @throws MalformedException If the header name or value are malformed
    */
   public void setHeader(String name, String value) throws MalformedException
   {
      if(readOnly) throw new IllegalStateException("src are readonly");
      Header newHeader = HeaderParser.parse(name, value);
      int pos = 0;
      for(int i = headers.size() - 1; i >= 0; i--) {
         Header header = headers.get(i);
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            if(header instanceof PositionHeader) {
               headers.add(i + 1, newHeader);
               return;
            } else {
               headers.remove(i);
            }
         } else if(Strings.equal(header.getName(), ":")) {
            pos = i;
         }
      }
      headers.add(pos + 1, newHeader);
   }


   /**
    * This will add the named header to its default position within the set
    * if and only if there are no existing headers with the given name.
    *
    * @throws MalformedException If the header name or value are malformed
    */
   public boolean setIfNotSet(String name, String value) throws MalformedException
   {
      if(readOnly) throw new IllegalStateException("src are readonly");
      Header newHeader = HeaderParser.parse(name, value);
      int pos = 0;
      for(int i = headers.size() - 1; i >= 0; i--) {
         Header header = headers.get(i);
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            if(header instanceof PositionHeader) {
               headers.add(i + 1, newHeader);
               return true;
            } else{
               return false;
            }
         } else if(Strings.equal(header.getName(), ":")) {
            pos = i;
         }
      }
      headers.add(pos + 1, newHeader);
      return true;
   }

   /**
    * This will remove all src with the given name returning the number
    * removed.
    */
   public int remove(String name)
   {
      int count = 0;
      for(int i = headers.size() - 1; i >= 0; i--) {
         Header header = headers.get(i);
         if(Strings.equalIgnoreCase(header.getName(), name)) {
            if(header instanceof PositionHeader) {
               return count;
            } else {
               headers.remove(i);
               count++;
            }
         } else if(count > 0) {
            return count;
         }
      }
      return count;
   }




// Helpers

   /**
    * Helper method to retrieve the Content-Length header value as a long.
    * <p/>
    * This will return -1 if the Content-Header header is not defined.
    */
   public long getContentLength()
   {
      Header contentLength = getHeader("Content-Length");
      return Integers.parse(toString(contentLength), -1);
   }









   @Override
   public boolean equals(Object obj)
   {
      if(obj instanceof Headers) {
         Headers o = (Headers) obj;
         return Objects.equal(headers, o.headers);
      }
      return false;
   }

   @Override
   public int hashCode()
   {
      return Objects.hash(headers);
   }


   /**
    * Returns the set of headers with one header per line
    */
   @Override
   public String toString()
   {
      StringBuilder builder = new StringBuilder();
      for(Enumeration<Header> e = headers(); e.hasMoreElements(); ) {
         builder.append(e.nextElement()).append("\r\n");
      }
      return builder.toString();
   }












   public static Headers create(List<String> raw)
   {
      List<Header> headers = new ArrayList<Header>();
      StringBuilder header = new StringBuilder();
      for(int i = raw.size() - 1; i >= 0; i--) {
         header.insert(0, raw.get(i));
         if(!Utils.isWhiteSpace(header.charAt(0))) {
            headers.add(0, HeaderParser.parse(Utils.getAndClear(header)));
         }
      }
      return new Headers(headers);
   }

   public static String toString(Header header)
   {
      return (header == null) ? null : header.getValue();
   }



   private static class FilteringEnumeration implements Enumeration<Header> {

      private final List<Header> src;
      private final Predicate<Object> filter;

      private Header  next;
      private int     i;

      private FilteringEnumeration(List<Header> headers, Predicate<Object> filter)
      {
         this.src = Objects.notNull(headers);
         this.filter = Objects.notNull(filter);
         this.next = findNext();
      }


      @Override public boolean hasMoreElements()
      {
         return next != null;
      }

      @Override public Header nextElement()
      {
         if(next == null) {
            throw new NoSuchElementException("enumeration exhausted");
         }
         Header result = next;
         next = findNext();
         return result;
      }

      private Header findNext()
      {
         while(i < src.size()) {
            Header header = src.get(i++);
            if(!filter.apply(header)) return header;
         }
         return null;
      }
   }

   private static class PositionHeader implements Header {

      private final String name;

      private PositionHeader(String name)
      {
         this.name = Objects.notNull(name);
      }

      @Override
      public String getName()
      {
         return name;
      }

      @Override
      public String getValue()
      {
         return null;
      }

      @Override
      public Type getType()
      {
         return Type.Raw;
      }

      @Override
      public int size()
      {
         return 0;
      }

      @Override
      public HeaderValue getValue(int index)
      {
         return null;
      }

      @Override
      public HeaderValue getValue(String name)
      {
         return null;
      }

   }
} public interface Checkable extends Selectable {

   public void handleCheckup() throws IOException;

} public interface ConnectHandler extends Selectable {

    public void handleConnect() throws IOException;

} public interface DataHandler extends Selectable {

   public void handleRead() throws IOException;
   public void handleWrite() throws IOException;

} public interface NioAction {

   public void execute(Selector selector)
      throws IOException;

   public Selectable getSelectable();

} public interface NioReader {

   public boolean readFrom(ByteBuffer src) throws IOException;

} public interface NioService {

   public void execute(NioAction action);

   public boolean isSelectorThread();

} public interface NioSession {





   /**
    * Get the current ready status of this session.
    */
   public ReadyState getReadyState();




   /**
    * Returns the socket address of the remote peer.
    *
    * @return The socket address of the remote peer.
    */
   public SocketAddress getRemoteAddress();

   /**
    * Returns the socket address of local machine which is associated with this
    * session.
    *
    * @return The local socket address for this session.
    */
   public SocketAddress getLocalAddress();

   /**
    * This will return the listen address that established this IOSession if it
    * is a server side session. Otherwise, if it is a client side session this
    * will return the same value as {@link #getRemoteAddress()}.
    *
    * @return The service address associated with this session.
    */
   public SocketAddress getServiceAddress();






   /**
    * Sets the value of a socket option.
    *
    * @param   name The socket option
    * @param   value The value of the socket option. A value of {@code null}
    *                may be a valid value for some socket options.
    * @return  This session
    *
    * @throws  UnsupportedOperationException If the socket option is not
    *             supported by this session
    * @throws  IllegalArgumentException If the value is not a valid value
    *             for this socket option
    * @throws IOException If an I/O error occurs
    * @see java.net.StandardSocketOptions
    * @see xpertss.net.OptionalSocketOptions
    * @see xpertss.net.SSLSocketOptions
    */
   <T> NioSession setOption(SocketOption<T> name, T value) throws IOException;

   /**
    * Returns the value of a socket option.
    *
    * @param   name The socket option
    * @return  The value of the socket option. A value of {@code null} may
    *          be a valid value for some socket options.
    * @throws  UnsupportedOperationException If the socket option is not
    *          supported by this channel
    * @throws  IOException If an I/O error occurs
    * @see java.net.StandardSocketOptions
    * @see xpertss.net.OptionalSocketOptions
    * @see xpertss.net.SSLSocketOptions
    */
   <T> T getOption(SocketOption<T> name) throws IOException;

   /**
    * Returns a set of the socket options supported by this channel.
    * <p/>
    * This method will continue to return the set of options even after
    * the session has been closed.
    *
    * @return  A set of the socket options supported by this session
    */
   Set<SocketOption<?>> supportedOptions();









   /**
    * Returns the time in millis when this session was created. The time is
    * always measured as the number of milliseconds since midnight, January
    * 1, 1970 UTC.
    *
    * @return The time in millis when this session was created.
    */
   public long getCreationTime();

   /**
    * Returns the time in millis when the last I/O operation occurred. The
    * time is always measured as the number of milliseconds since midnight,
    * January 1, 1970 UTC.
    *
    * @return The time in millis when the last I/O operation occured.
    */
   public long getLastIoTime();

   /**
    * Returns the time in millis when the last read operation occurred. The
    * time is always measured as the number of milliseconds since midnight,
    * January 1, 1970 UTC.
    *
    * @return The time in millis when the last read operation occurred
    */
   public long getLastReadTime();

   /**
    * Returns the time in millis when the lst write operation occurred. The
    * time is always measured as the number of milliseconds since midnight,
    * January 1, 1970 UTC.
    *
    * @return The time in millis when the lst write operation occurred
    */
   public long getLastWriteTime();





   /**
    * Return the number of bytes written to this session.
    */
   public long getBytesWritten();

   /**
    * Return the number of bytes read from this session.
    */
   public long getBytesRead();





   /**
    * Returns the value of user-defined attribute of this session.
    *
    * @param key the key of the attribute
    * @return <tt>null</tt> if there is no attribute with the specified key
    */
   public Object getAttribute(String key);

   /**
    * Sets a user-defined attribute.
    *
    * @param key the key of the attribute
    * @param value the value of the attribute
    */
   public void setAttribute(String key, Object value);

   /**
    * Removes a user-defined attribute with the specified key.
    *
    * @param key The key identifying the attribute to remove
    */
   public void removeAttribute(String key);

   /**
    * Returns <tt>true</tt> if this session contains the attribute with
    * the specified <tt>key</tt>.
    *
    * @param key The key identifying the attribute to check existance of
    * @return true if the named attribute exists in this session
    */
   public boolean hasAttribute(String key);

   /**
    * Returns the set of keys of all user-defined attributes.
    *
    * @return A set of attribute keys currently defined
    */
   public Set<String> getAttributeKeys();




} public class NioStats {

   private long time = System.currentTimeMillis();
   private long count;

   public void record(long amount)
   {
      if(amount > 0) {
         time = System.currentTimeMillis();
         count += amount;
      }
   }

   public long getCount()
   {
      return count;
   }

   public long getTime()
   {
      return time;
   }

} public interface NioWriter {

   public boolean writeTo(ByteBuffer dst) throws IOException;

} public enum ReadyState {

   Open,

   Connecting,

   Connected,

   Closing,

   Closed

} public interface Selectable {

   public SelectableChannel getChannel();

   public void shutdown(Exception ex);

} public class UserAgent {

   private final BitSet bitset = new BitSet(3);

   private String appName;
   private Version appVersion;

   /**
    * Accessible zero argument constructor for class wishing to extend and
    * override this implementation.
    */
   protected UserAgent() { }

   private UserAgent(String appName, Version appVersion)
   {
      this.appName = Strings.notEmpty(appName, "appName must be defined");
      this.appVersion = Objects.notNull(appVersion, "appVersion");
   }


   /**
    * Include the name of this RTSP library and its version in the user
    * agent header sent to the server with each request.
    */
   public void includeLibrary(boolean value)
   {
      bitset.set(0, !value);
   }

   /**
    * Returns {@code true} if the library portion is included in the user
    * agent string sent to the server with each request.
    */
   public boolean isLibraryIncluded()
   {
      return !bitset.get(0);
   }



   /**
    * Include the version of Java this library is running on in the user
    * agent header sent to the server with each request.
    */
   public void includeJava(boolean value)
   {
      bitset.set(1, !value);
   }

   /**
    * Returns {@code true} if the Java portion is included in the user
    * agent string sent to the server with each request.
    */
   public boolean isJavaIncluded()
   {
      return !bitset.get(1);
   }



   /**
    * Include the type of CPU this user agent is running on in the user
    * agent header sent to the server with each request.
    */
   public void includeCpu(boolean value)
   {
      bitset.set(2, !value);
   }

   /**
    * Returns {@code true} if the cpu portion is included in the user
    * agent string sent to the server with each request.
    */
   public boolean isCpuIncluded()
   {
      return !bitset.get(2);
   }


   @Override
   public String toString()
   {
      StringBuilder builder = new StringBuilder();
      builder.append(String.format("%s/%s", appName, appVersion));

      String osName = System.getProperty("os.name").split("\\s+")[0];
      Version osVersion = Platform.osVersion();

      builder.append(String.format(" (%s/%s", osName, osVersion));
      if(!bitset.get(2)) {
         String osArch = System.getProperty("os.arch");
         builder.append(String.format("; %s", osArch));
      }
      if(!bitset.get(1)) {
         Version vmVersion = Platform.javaVersion();
         builder.append(String.format("; Java/%s", vmVersion));
      }
      builder.append(")");
      if(!bitset.get(0)) {
         Config config = Config.load("version.properties", false);
         String libVersion = config.getProperty("version");
         builder.append(String.format(" XpertRTSP/%s", libVersion));
      }

      return builder.toString();
   }


   /**
    * Create a default UserAgent object with the specified application
    * name and version.
    *
    * @param appName The application name
    * @param appVersion The application version
    */
   public static UserAgent create(String appName, Version appVersion)
   {
      return new UserAgent(appName, appVersion);
   }

} public class Utils {

   /**
    * Creates and returns an {@link InetSocketAddress} that represents the authority
    * of the given {@link java.net.URI}. If the {@link java.net.URI} does not define
    * a port then the specified default port is used instead.
    *
    * @throws NullPointerException if uri is {@code null}
    * @throws IllegalArgumentException if the port is outside the range 1 - 65545
    */
   public static SocketAddress createSocketAddress(URI uri, int defPort)
   {
      String authority = uri.getAuthority();
      if(Strings.isEmpty(authority))
         throw new IllegalArgumentException("uri does not define an authority");
      String[] parts = authority.split(":");
      int port = defPort;
      if(parts.length == 2) {
         port = Integers.parse(parts[1], defPort);
         Numbers.within(1, 65545, port, String.format("%d is an invalid port", port));
      }
      return new InetSocketAddress(parts[0], port);
   }




   public static <T> T get(T[] items, int index)
   {
      if(items == null || index < 0) return null;
      return (items.length > index) ? items[index] : null;
   }


   public static String trimAndClear(StringBuilder buf)
   {
      return getAndClear(buf).trim();
   }

   public static String getAndClear(StringBuilder buf)
   {
      try {
         return buf.toString();
      } finally {
         buf.setLength(0);
      }
   }


   public static long computeTimeout(int millis)
   {
      if(millis == 0) millis = 60000;  // default timeout of 60 seconds
      return System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(millis);
   }






   public static String consume(CharBuffer buf, boolean trim)
   {
      buf.flip();
      int offset = 0; int end = buf.limit();
      if(trim) {
         while(offset < end && buf.get(offset) == ' ') offset++;
         while(end > offset && buf.get(end - 1) == ' ') end--;
      }
      char[] result = new char[end-offset];
      buf.position(offset);
      buf.get(result).clear();
      return new String(result);
   }


   public static boolean isWhiteSpace(char c)
   {
      return (c == '\t' || c == '\r' || c == '\n' || c == ' ');
   }


   public static int maxIfZero(int value)
   {
      return (value == 0) ? Integer.MAX_VALUE : value;
   }


   public static String getHeader(RtspResponse response, String name)
   {
      return Headers.toString(response.getHeaders().getHeader(name));
   }

} public static final SocketOption<Integer> SO_TIMEOUT =
   new StdSocketOption<Integer>("SO_TIMEOUT", Integer.class); public enum ReadyState {

   Open,

   Connecting,

   Connected,

   Closing,

   Closed

} public enum RtspMethod {

   /**
    * The OPTIONS method represents a request for information about the
    * communication options available on the request/response chain
    * identified by the Request-URI. This method allows the client to
    * determine the options and/or requirements associated with a resource,
    * or the capabilities of a server, without implying a resource action
    * or initiating a resource retrieval.
    */
   Options(false),

   /**
    * When sent from client to server, ANNOUNCE posts the description of a
    * presentation or media object identified by the request URL to a
    * server. When sent from server to client, ANNOUNCE updates the session
    * description in real-time.
    */
   Announce(true) {
      @Override public RtspRequest createRequest(URI uri)
      {
         throw new UnsupportedOperationException("announce is not yet supported");
      }
   },

   /**
    * The DESCRIBE method retrieves the description of a presentation or
    * media object identified by the request URL from a server. It may use
    * the Accept header to specify the description formats that the client
    * understands. The server responds with a description of the requested
    * resource. The DESCRIBE reply-response pair constitutes the media
    * initialization phase of RTSP.
    */
   Describe(false),

   /**
    * The SETUP request for a URI specifies the transport mechanism to be
    * used for the streamed media.
    */
   Setup(false),

   /**
    * The PLAY method tells the server to start sending data via the
    * mechanism specified in SETUP. A client MUST NOT issue a PLAY request
    * until any outstanding SETUP requests have been acknowledged as
    * successful.
    */
   Play(false),

   /**
    * This method initiates recording a range of media data according to
    * the presentation description.
    */
   Record(false) {
      @Override public RtspRequest createRequest(URI uri)
      {
         throw new UnsupportedOperationException("record not yet supported");
      }
   },

   /**
    * The PAUSE request causes the stream delivery to be interrupted
    * (halted) temporarily. If the request URL names a stream, only
    * playback and recording of that stream is halted. For example, for
    * audio, this is equivalent to muting. If the request URL names a
    * presentation or group of streams, delivery of all currently active
    * streams within the presentation or group is halted.
    */
   Pause(false),

   /**
    * The TEARDOWN request stops the stream delivery for the given URI,
    * freeing the resources associated with it. If the URI is the
    * presentation URI for this presentation, any RTSP session identifier
    * associated with the session is no longer valid. Unless all transport
    * parameters are defined by the session description, a SETUP request
    * has to be issued before the session can be played again.
    */
   Teardown(false);



   private boolean supportsEntity;

   private RtspMethod(boolean supportsEntity)
   {
      this.supportsEntity = supportsEntity;
   }


   /**
    * Create a request using the current request method to the default uri
    * specified when the session was opened.
    */
   public RtspRequest createRequest()
   {
      return createRequest(null);
   }

   /**
    * Create a request using the current request method to the specified uri.
    */
   public RtspRequest createRequest(URI uri)
   {
      return new RtspRequest(this, uri);
   }

   /**
    * Allows an entity to be sent to the server.
    */
   public boolean supportsEntity() { return supportsEntity; }


}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import xpertss.io.Buffers;
import xpertss.io.NIOUtils;
import xpertss.lang.Booleans;
import xpertss.lang.Integers;
import xpertss.lang.Numbers;
import xpertss.lang.Objects;
import xpertss.lang.Strings;
import xpertss.mime.Headers;
import xpertss.nio.Checkable;
import xpertss.nio.ConnectHandler;
import xpertss.nio.DataHandler;
import xpertss.nio.NioAction;
import xpertss.nio.NioReader;
import xpertss.nio.NioService;
import xpertss.nio.NioSession;
import xpertss.nio.NioStats;
import xpertss.nio.NioWriter;
import xpertss.nio.ReadyState;
import xpertss.nio.Selectable;
import xpertss.utils.UserAgent;
import xpertss.utils.Utils;
import java.io.EOFException;
import java.io.IOException;
import java.net.ConnectException;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import static java.nio.channels.SelectionKey.*;
import static xpertss.net.OptionalSocketOptions.SO_TIMEOUT;
import static xpertss.nio.ReadyState.*;
import static xpertss.rtsp.RtspMethod.*;
package xpertss.rtsp;




/**
 * This RtpSession impl is designed to support a streaming player not a
 * streaming producer.
 */
public class RtspSession implements NioSession, DataHandler, ConnectHandler, Checkable {


   private final ConcurrentMap<String,Object> attributes = new ConcurrentHashMap<>();
   private final Deque<ResponseReader> readers = new ConcurrentLinkedDeque<>();
   private final Queue<NioWriter> writeQueue = new ConcurrentLinkedQueue<>();