1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.libraries.rcs.simpleclient.protocol.sdp;
18 
19 import com.google.auto.value.AutoValue;
20 import com.google.common.base.Splitter;
21 import com.google.common.collect.ImmutableList;
22 
23 import java.io.BufferedReader;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.text.ParseException;
28 import java.util.List;
29 import java.util.Optional;
30 import java.util.OptionalInt;
31 
32 /**
33  * The SDP implementation as per RFC 4566. This class supports minimal fields that is required to
34  * represent MSRP session.
35  */
36 @AutoValue
37 public abstract class SimpleSdpMessage {
38     private static final String CRLF = "\r\n";
39 
40     private static final String PREFIX_VERSION = "v";
41     private static final String PREFIX_ORIGIN = "o";
42     private static final String PREFIX_SESSION = "s";
43     private static final String PREFIX_CONNECTION = "c";
44     private static final String PREFIX_TIME = "t";
45     private static final String PREFIX_MEDIA = "m";
46     private static final String PREFIX_ATTRIBUTE = "a";
47     private static final String EQUAL = "=";
48 
parse(InputStream stream)49     public static SimpleSdpMessage parse(InputStream stream) throws ParseException, IOException {
50         Builder builder = new AutoValue_SimpleSdpMessage.Builder();
51         SdpMedia.Builder currentMediaBuilder = null;
52         BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
53 
54         String line = reader.readLine();
55         while (line != null) {
56             List<String> parts = Splitter.on("=").trimResults().limit(2).splitToList(line);
57             if (parts.size() != 2) {
58                 throw new ParseException("Invalid SDP format", 0);
59             }
60             String prefix = parts.get(0);
61             String value = parts.get(1);
62 
63             switch (prefix) {
64                 case PREFIX_VERSION:
65                     builder.setVersion(value);
66                     break;
67                 case PREFIX_ORIGIN:
68                     builder.setOrigin(value);
69                     break;
70                 case PREFIX_SESSION:
71                     builder.setSession(value);
72                     break;
73                 case PREFIX_CONNECTION:
74                     builder.setConnection(value);
75                     break;
76                 case PREFIX_TIME:
77                     builder.setTime(value);
78                     break;
79                 case PREFIX_MEDIA:
80                     if (currentMediaBuilder != null) {
81                         builder.addMedia(currentMediaBuilder.build());
82                     }
83                     currentMediaBuilder = SdpMedia.parseMediaLine(value);
84                     break;
85                 case PREFIX_ATTRIBUTE:
86                     if (currentMediaBuilder != null) {
87                         List<String> kv = Splitter.on(":").trimResults().limit(2).splitToList(
88                                 value);
89                         currentMediaBuilder.addAttribute(kv.get(0), kv.size() < 2 ? "" : kv.get(1));
90                     }
91                     break;
92                 default:
93                     // Rest of the fields are ignored as they're not used for describing MSRP
94                     // session.
95                     break;
96             }
97             line = reader.readLine();
98         }
99 
100         if (currentMediaBuilder != null) {
101             builder.addMedia(currentMediaBuilder.build());
102         }
103 
104         return builder.build();
105     }
106 
107     private static String encodeLine(String prefix, String value) {
108         return prefix + EQUAL + value + CRLF;
109     }
110 
111     public static Builder newBuilder() {
112         return new AutoValue_SimpleSdpMessage.Builder();
113     }
114 
115     public abstract String version();
116 
117     public abstract String origin();
118 
119     public abstract String session();
120 
121     public abstract String connection();
122 
123     public abstract String time();
124 
125     public abstract ImmutableList<SdpMedia> media();
126 
127     /** Return the IP address in the connection line. */
128     public Optional<String> getAddress() {
129         if (connection() == null) {
130             return Optional.empty();
131         }
132 
133         List<String> parts = Splitter.on(" ").limit(3).trimResults().splitToList(connection());
134         if (parts.size() != 3) {
135             return Optional.empty();
136         }
137 
138         return Optional.of(parts.get(2));
139     }
140 
141     /** Return the port in the first media line. */
142     public OptionalInt getPort() {
143         if (media().isEmpty()) {
144             return OptionalInt.empty();
145         }
146 
147         return OptionalInt.of(media().get(0).port());
148     }
149 
150     public Optional<String> getPath() {
151         if (media().isEmpty()) {
152             return Optional.empty();
153         }
154 
155         return Optional.ofNullable(media().get(0).attributes().get("path"));
156     }
157 
158     /** Encode the entire SDP fields as a string. */
159     public String encode() {
160         StringBuilder builder = new StringBuilder();
161         builder
162                 .append(encodeLine(PREFIX_VERSION, version()))
163                 .append(encodeLine(PREFIX_ORIGIN, origin()))
164                 .append(encodeLine(PREFIX_SESSION, session()))
165                 .append(encodeLine(PREFIX_CONNECTION, connection()))
166                 .append(encodeLine(PREFIX_TIME, time()));
167 
168         for (SdpMedia media : media()) {
169             builder.append(media.encode());
170         }
171 
172         return builder.toString();
173     }
174 
175     @AutoValue.Builder
176     public abstract static class Builder {
177         public abstract Builder setVersion(String version);
178 
179         public abstract Builder setOrigin(String origin);
180 
181         public abstract Builder setSession(String session);
182 
183         public abstract Builder setConnection(String connection);
184 
185         public abstract Builder setTime(String connection);
186 
187         public abstract ImmutableList.Builder<SdpMedia> mediaBuilder();
188 
189         public Builder addMedia(SdpMedia media) {
190             mediaBuilder().add(media);
191             return this;
192         }
193 
194         public abstract SimpleSdpMessage build();
195     }
196 }
197