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 android.telephony.ims;
18 
19 import static java.nio.charset.StandardCharsets.UTF_8;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.os.Build;
25 import android.os.Parcel;
26 import android.os.Parcelable;
27 import android.text.TextUtils;
28 
29 import com.android.internal.telephony.SipMessageParsingUtils;
30 
31 import java.util.Arrays;
32 import java.util.Objects;
33 
34 /**
35  * Represents a partially encoded SIP message. See RFC 3261 for more information on how SIP
36  * messages are structured and used.
37  * <p>
38  * The SIP message is represented in a partially encoded form in order to allow for easier
39  * verification and should not be used as a generic SIP message container.
40  * @hide
41  */
42 @SystemApi
43 public final class SipMessage implements Parcelable {
44     // Should not be set to true for production!
45     private static final boolean IS_DEBUGGING = Build.IS_ENG;
46     private static final String CRLF = "\r\n";
47 
48     private final String mStartLine;
49     private final String mHeaderSection;
50     private final byte[] mContent;
51     private final String mViaBranchParam;
52     private final String mCallIdParam;
53 
54     /**
55      * Represents a partially encoded SIP message.
56      *
57      * @param startLine The start line of the message, containing either the request-line or
58      *                  status-line.
59      * @param headerSection A String containing the full unencoded SIP message header.
60      * @param content SIP message body.
61      */
SipMessage(@onNull String startLine, @NonNull String headerSection, @NonNull byte[] content)62     public SipMessage(@NonNull String startLine, @NonNull String headerSection,
63             @NonNull byte[] content) {
64         Objects.requireNonNull(startLine, "Required parameter is null: startLine");
65         Objects.requireNonNull(headerSection, "Required parameter is null: headerSection");
66         Objects.requireNonNull(content, "Required parameter is null: content");
67 
68         mStartLine = startLine;
69         mHeaderSection = headerSection;
70         mContent = content;
71 
72         mViaBranchParam = SipMessageParsingUtils.getTransactionId(mHeaderSection);
73         if (TextUtils.isEmpty(mViaBranchParam)) {
74             throw new IllegalArgumentException("header section MUST contain a branch parameter "
75                     + "inside of the Via header.");
76         }
77         mCallIdParam = SipMessageParsingUtils.getCallId(mHeaderSection);
78     }
79 
80     /**
81      * Private constructor used only for unparcelling.
82      */
SipMessage(Parcel source)83     private SipMessage(Parcel source) {
84         mStartLine = source.readString();
85         mHeaderSection = source.readString();
86         mContent = new byte[source.readInt()];
87         source.readByteArray(mContent);
88         mViaBranchParam = source.readString();
89         mCallIdParam = source.readString();
90     }
91 
92     /**
93      * @return The start line of the SIP message, which contains either the request-line or
94      * status-line.
95      */
getStartLine()96     public @NonNull String getStartLine() {
97         return mStartLine;
98     }
99 
100     /**
101      * @return The full, unencoded header section of the SIP message.
102      */
getHeaderSection()103     public @NonNull String getHeaderSection() {
104         return mHeaderSection;
105     }
106 
107     /**
108      * @return the SIP message body.
109      */
getContent()110     public @NonNull byte[] getContent() {
111         return mContent;
112     }
113 
114     /**
115      * @return the branch parameter enclosed in the Via header key's value. See RFC 3261 section
116      * 20.42 for more information on the Via header.
117      */
getViaBranchParameter()118     public @NonNull String getViaBranchParameter() {
119         return mViaBranchParam;
120     }
121 
122     /**
123      * @return the value associated with the call-id header of this SIP message. See RFC 3261
124      * section 20.8 for more information on the call-id header. If {@code null}, then there was no
125      * call-id header found in this SIP message's headers.
126      */
getCallIdParameter()127     public @Nullable String getCallIdParameter() {
128         return mCallIdParam;
129     }
130 
131     @Override
describeContents()132     public int describeContents() {
133         return 0;
134     }
135 
136     @Override
writeToParcel(@onNull Parcel dest, int flags)137     public void writeToParcel(@NonNull Parcel dest, int flags) {
138         dest.writeString(mStartLine);
139         dest.writeString(mHeaderSection);
140         dest.writeInt(mContent.length);
141         dest.writeByteArray(mContent);
142         dest.writeString(mViaBranchParam);
143         dest.writeString(mCallIdParam);
144     }
145 
146     public static final @NonNull Creator<SipMessage> CREATOR = new Creator<SipMessage>() {
147         @Override
148         public SipMessage createFromParcel(Parcel source) {
149             return new SipMessage(source);
150         }
151 
152         @Override
153         public SipMessage[] newArray(int size) {
154             return new SipMessage[size];
155         }
156     };
157 
158     @Override
toString()159     public String toString() {
160         StringBuilder b = new StringBuilder();
161         b.append("StartLine: [");
162         if (IS_DEBUGGING) {
163             b.append(mStartLine);
164         } else {
165             b.append(sanitizeStartLineRequest(mStartLine));
166         }
167         b.append("], Header: [");
168         if (IS_DEBUGGING) {
169             b.append(mHeaderSection);
170         } else {
171             // only identify transaction id/call ID when it is available.
172             b.append("***");
173         }
174         b.append("], Content: ");
175         b.append(getContent().length == 0 ? "[NONE]" : "[NOT SHOWN]");
176         return b.toString();
177     }
178 
179     /**
180      * Detect if this is a REQUEST and redact Request-URI portion here, as it contains PII.
181      */
sanitizeStartLineRequest(String startLine)182     private String sanitizeStartLineRequest(String startLine) {
183         if (!SipMessageParsingUtils.isSipRequest(startLine)) return startLine;
184         String[] splitLine = startLine.split(" ");
185         return splitLine[0] + " <Request-URI> " + splitLine[2];
186     }
187 
188     @Override
equals(Object o)189     public boolean equals(Object o) {
190         if (this == o) return true;
191         if (o == null || getClass() != o.getClass()) return false;
192         SipMessage that = (SipMessage) o;
193         return mStartLine.equals(that.mStartLine)
194                 && mHeaderSection.equals(that.mHeaderSection)
195                 && Arrays.equals(mContent, that.mContent);
196     }
197 
198     @Override
hashCode()199     public int hashCode() {
200         int result = Objects.hash(mStartLine, mHeaderSection);
201         result = 31 * result + Arrays.hashCode(mContent);
202         return result;
203     }
204 
205     /**
206      * According RFC-3261 section 7, SIP is a text protocol and uses the UTF-8 charset. Its format
207      * consists of a start-line, one or more header fields, an empty line indicating the end of the
208      * header fields, and an optional message-body.
209      *
210      * <p>
211      * Returns a byte array with UTF-8 format representation of the encoded SipMessage.
212      *
213      * @return byte array with UTF-8 format representation of the encoded SipMessage.
214      */
toEncodedMessage()215     public @NonNull byte[] toEncodedMessage() {
216         byte[] header = new StringBuilder()
217                 .append(mStartLine)
218                 .append(mHeaderSection)
219                 .append(CRLF)
220                 .toString().getBytes(UTF_8);
221         byte[] sipMessage = new byte[header.length + mContent.length];
222         System.arraycopy(header, 0, sipMessage, 0, header.length);
223         System.arraycopy(mContent, 0, sipMessage, header.length, mContent.length);
224         return sipMessage;
225     }
226 }
227