/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.telephony.ims; import android.annotation.IntDef; import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.InetAddresses; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.telephony.ims.stub.SipDelegate; import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Objects; /** * The IMS registration and other attributes that the {@link SipDelegateConnection} used by the * IMS application will need to be aware of to correctly generate outgoing {@link SipMessage}s. *
* The IMS service must generate new instances of this configuration as the IMS configuration * managed by the IMS service changes. Along with each {@link SipDelegateConfiguration} instance * containing the configuration is the "version", which should be incremented every time a new * {@link SipDelegateConfiguration} instance is created. The {@link SipDelegateConnection} will * include the version of the {@link SipDelegateConfiguration} instance that it used in order for * the {@link SipDelegate} to easily identify if the IMS application used a now stale configuration * to generate the {@link SipMessage} and return * {@link SipDelegateManager#MESSAGE_FAILURE_REASON_STALE_IMS_CONFIGURATION} in * {@link DelegateMessageCallback#onMessageSendFailure(String, int)} so that the IMS application can * regenerate that {@link SipMessage} using the correct {@link SipDelegateConfiguration} * instance. *
* Every time the IMS configuration state changes in the IMS service, a full configuration should * be generated. The new {@link SipDelegateConfiguration} instance should not be an incremental * update. * @see Builder * @hide */ @SystemApi public final class SipDelegateConfiguration implements Parcelable { /** * The SIP transport uses UDP. */ public static final int SIP_TRANSPORT_UDP = 0; /** * The SIP transport uses TCP. */ public static final int SIP_TRANSPORT_TCP = 1; /**@hide*/ @IntDef(prefix = "SIP_TRANSPORT_", value = { SIP_TRANSPORT_UDP, SIP_TRANSPORT_TCP }) @Retention(RetentionPolicy.SOURCE) public @interface TransportType {} /** * The value returned by {@link #getMaxUdpPayloadSizeBytes()} when it is not defined. */ public static final int UDP_PAYLOAD_SIZE_UNDEFINED = -1; /** * SIP over IPSec configuration */ public static final class IpSecConfiguration { private final int mLocalTxPort; private final int mLocalRxPort; private final int mLastLocalTxPort; private final int mRemoteTxPort; private final int mRemoteRxPort; private final int mLastRemoteTxPort; private final String mSecurityHeader; /** * Describes the SIP over IPSec configuration the SipDelegate will need to use. * * @param localTxPort Local SIP port number used to send traffic. * @param localRxPort Local SIP port number used to receive traffic. * @param lastLocalTxPort Local SIP port number used for the previous IPsec security * association. * @param remoteTxPort Remote port number used by the SIP server to send SIP traffic. * @param remoteRxPort Remote port number used by the SIP server to receive incoming SIP * traffic. * @param lastRemoteTxPort Remote port number used by the SIP server to send SIP traffic on * the previous IPSec security association. * @param securityHeader The value of the SIP security verify header. */ public IpSecConfiguration(int localTxPort, int localRxPort, int lastLocalTxPort, int remoteTxPort, int remoteRxPort, int lastRemoteTxPort, @NonNull String securityHeader) { mLocalTxPort = localTxPort; mLocalRxPort = localRxPort; mLastLocalTxPort = lastLocalTxPort; mRemoteTxPort = remoteTxPort; mRemoteRxPort = remoteRxPort; mLastRemoteTxPort = lastRemoteTxPort; mSecurityHeader = securityHeader; } /** * @return The local SIP port number used to send traffic. */ public int getLocalTxPort() { return mLocalTxPort; } /** * @return The Local SIP port number used to receive traffic. */ public int getLocalRxPort() { return mLocalRxPort; } /** * @return The last local SIP port number used for the previous IPsec security association. */ public int getLastLocalTxPort() { return mLastLocalTxPort; } /** * @return The remote port number used by the SIP server to send SIP traffic. */ public int getRemoteTxPort() { return mRemoteTxPort; } /** * @return the remote port number used by the SIP server to receive incoming SIP traffic. */ public int getRemoteRxPort() { return mRemoteRxPort; } /** * @return the remote port number used by the SIP server to send SIP traffic on the previous * IPSec security association. */ public int getLastRemoteTxPort() { return mLastRemoteTxPort; } /** * @return The value of the SIP security verify header. */ public @NonNull String getSipSecurityVerifyHeader() { return mSecurityHeader; } /** * Helper for parcelling this object. * @hide */ public void addToParcel(Parcel dest) { dest.writeInt(mLocalTxPort); dest.writeInt(mLocalRxPort); dest.writeInt(mLastLocalTxPort); dest.writeInt(mRemoteTxPort); dest.writeInt(mRemoteRxPort); dest.writeInt(mLastRemoteTxPort); dest.writeString(mSecurityHeader); } /** * Helper for unparcelling this object. * @hide */ public static IpSecConfiguration fromParcel(Parcel source) { return new IpSecConfiguration(source.readInt(), source.readInt(), source.readInt(), source.readInt(), source.readInt(), source.readInt(), source.readString()); } @Override public String toString() { return "IpSecConfiguration{" + "localTx=" + mLocalTxPort + ", localRx=" + mLocalRxPort + ", lastLocalTx=" + mLastLocalTxPort + ", remoteTx=" + mRemoteTxPort + ", remoteRx=" + mRemoteRxPort + ", lastRemoteTx=" + mLastRemoteTxPort + ", securityHeader=" + mSecurityHeader + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; IpSecConfiguration that = (IpSecConfiguration) o; return mLocalTxPort == that.mLocalTxPort && mLocalRxPort == that.mLocalRxPort && mLastLocalTxPort == that.mLastLocalTxPort && mRemoteTxPort == that.mRemoteTxPort && mRemoteRxPort == that.mRemoteRxPort && mLastRemoteTxPort == that.mLastRemoteTxPort && Objects.equals(mSecurityHeader, that.mSecurityHeader); } @Override public int hashCode() { return Objects.hash(mLocalTxPort, mLocalRxPort, mLastLocalTxPort, mRemoteTxPort, mRemoteRxPort, mLastRemoteTxPort, mSecurityHeader); } } /** * Creates a new instance of {@link SipDelegateConfiguration} composed from optional * configuration items. */ public static final class Builder { private final SipDelegateConfiguration mConfig; /** * * @param version The version associated with the {@link SipDelegateConfiguration} instance * being built. See {@link #getVersion()} for more information. * @param transportType The transport type to use for SIP signalling. * @param localAddr The local socket address used for SIP traffic. * @param serverAddr The SIP server or P-CSCF default IP address for sip traffic. * @see InetAddresses#parseNumericAddress(String) for how to create an * {@link InetAddress} without requiring a DNS lookup. */ public Builder(@IntRange(from = 0) long version, @TransportType int transportType, @NonNull InetSocketAddress localAddr, @NonNull InetSocketAddress serverAddr) { mConfig = new SipDelegateConfiguration(version, transportType, localAddr, serverAddr); } /** * Create a new {@link SipDelegateConfiguration} instance with the same exact configuration * as the passed in instance, except for the version parameter, which will be incremented * by 1. *
* This method is useful for cases where only a small subset of configurations have changed * and the new configuration is based off of the old configuration. * @param c The older {@link SipDelegateConfiguration} instance to base this instance's * configuration off of. */ public Builder(@NonNull SipDelegateConfiguration c) { mConfig = c.copyAndIncrementVersion(); } /** * Sets whether or not SIP compact form is enabled for the associated SIP delegate. *
* If unset, this configuration defaults to {@code false}. * @param isEnabled {@code true} if SIP compact form is enabled for the associated SIP * Delegate, {@code false} if it is not. * @return this Builder instance with the compact form configuration set. */ public @NonNull Builder setSipCompactFormEnabled(boolean isEnabled) { mConfig.mIsSipCompactFormEnabled = isEnabled; return this; } /** * Sets whether or not underlying SIP keepalives are enabled for the associated SIP * delegate. *
* If unset, this configuration defaults to {@code false}. * @param isEnabled {@code true} if SIP keepalives are enabled for the associated SIP * Delegate, {@code false} if it is not. * @return this Builder instance with the new configuration set. */ public @NonNull Builder setSipKeepaliveEnabled(boolean isEnabled) { mConfig.mIsSipKeepaliveEnabled = isEnabled; return this; } /** * Sets the max SIP payload size in bytes to be sent on UDP. If the SIP message payload is * greater than the max UDP payload size, then TCP must be used. *
* If unset, this configuration defaults to {@link #UDP_PAYLOAD_SIZE_UNDEFINED}, or no * size specified. * @param size The maximum SIP payload size in bytes for UDP. * @return this Builder instance with the new configuration set. */ public @NonNull Builder setMaxUdpPayloadSizeBytes(@IntRange(from = 1) int size) { mConfig.mMaxUdpPayloadSize = size; return this; } /** * Sets the IMS public user identifier. *
* If unset, this configuration defaults to {@code null}, or no identifier specified. * @param id The IMS public user identifier. * @return this Builder instance with the new configuration set. */ public @NonNull Builder setPublicUserIdentifier(@Nullable String id) { mConfig.mPublicUserIdentifier = id; return this; } /** * Sets the IMS private user identifier. *
* If unset, this configuration defaults to {@code null}, or no identifier specified. * @param id The IMS private user identifier. * @return this Builder instance with the new configuration set. */ public @NonNull Builder setPrivateUserIdentifier(@Nullable String id) { mConfig.mPrivateUserIdentifier = id; return this; } /** * Sets the IMS home domain. *
* If unset, this configuration defaults to {@code null}, or no domain specified. * @param domain The IMS home domain. * @return this Builder instance with the new configuration set. */ public @NonNull Builder setHomeDomain(@Nullable String domain) { mConfig.mHomeDomain = domain; return this; } /** * Sets the IMEI of the associated device. *
* Application can include the Instance-ID feature tag {@code "+sip.instance"} in the
* Contact header with a value of the device IMEI in the form
* {@code "urn:gsma:imei:
* If unset, this configuration defaults to {@code null}, or no IMEI string specified.
* @param imei The IMEI of the device.
* @return this Builder instance with the new configuration set.
*/
public @NonNull Builder setImei(@Nullable String imei) {
mConfig.mImei = imei;
return this;
}
/**
* Set the optional {@link IpSecConfiguration} instance used if the associated SipDelegate
* is communicating over IPSec.
*
* If unset, this configuration defaults to {@code null}
* @param c The IpSecConfiguration instance to set.
* @return this Builder instance with IPSec configuration set.
*/
public @NonNull Builder setIpSecConfiguration(@Nullable IpSecConfiguration c) {
mConfig.mIpSecConfiguration = c;
return this;
}
/**
* Describes the Device's Public IP Address and port that is set when Network Address
* Translation is enabled and the device is behind a NAT.
*
* If unset, this configuration defaults to {@code null}
* @param addr The {@link InetAddress} representing the device's public IP address and port
* when behind a NAT.
* @return this Builder instance with the new configuration set.
* @see InetAddresses#parseNumericAddress(String) For an example of how to create an
* instance of {@link InetAddress} without causing a DNS lookup.
*/
public @NonNull Builder setNatSocketAddress(@Nullable InetSocketAddress addr) {
mConfig.mNatAddress = addr;
return this;
}
/**
* Sets the optional URI of the device's Globally routable user-agent URI (GRUU) if this
* feature is enabled for the SIP delegate.
*
* If unset, this configuration defaults to {@code null}
* @param uri The GRUU to set.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setPublicGruuUri(@Nullable Uri uri) {
mConfig.mGruu = uri;
return this;
}
/**
* Sets the SIP authentication header value.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP authentication header's value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipAuthenticationHeader(@Nullable String header) {
mConfig.mSipAuthHeader = header;
return this;
}
/**
* Sets the SIP authentication nonce.
*
* If unset, this configuration defaults to {@code null}.
* @param nonce The SIP authentication nonce.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipAuthenticationNonce(@Nullable String nonce) {
mConfig.mSipAuthNonce = nonce;
return this;
}
/**
* Sets the SIP service route header value.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP service route header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipServiceRouteHeader(@Nullable String header) {
mConfig.mServiceRouteHeader = header;
return this;
}
/**
* Sets the SIP path header value.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP path header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipPathHeader(@Nullable String header) {
mConfig.mPathHeader = header;
return this;
}
/**
* Sets the SIP User-Agent header value.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP User-Agent header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipUserAgentHeader(@Nullable String header) {
mConfig.mUserAgentHeader = header;
return this;
}
/**
* Sets the SIP Contact header's User parameter value.
*
* If unset, this configuration defaults to {@code null}.
* @param param The SIP Contact header's User parameter value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipContactUserParameter(@Nullable String param) {
mConfig.mContactUserParam = param;
return this;
}
/**
* Sets the SIP P-Access-Network-Info (P-ANI) header value. Populated for networks that
* require this information to be provided as part of outgoing SIP messages.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP P-ANI header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipPaniHeader(@Nullable String header) {
mConfig.mPaniHeader = header;
return this;
}
/**
* Sets the SIP P-Last-Access-Network-Info (P-LANI) header value. Populated for
* networks that require this information to be provided as part of outgoing SIP messages.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP P-LANI header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipPlaniHeader(@Nullable String header) {
mConfig.mPlaniHeader = header;
return this;
}
/**
* Sets the SIP Cellular-Network-Info (CNI) header value (See 3GPP 24.229, section 7.2.15),
* populated for networks that require this information to be provided as part of outgoing
* SIP messages.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP P-LANI header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipCniHeader(@Nullable String header) {
mConfig.mCniHeader = header;
return this;
}
/**
* Sets the SIP P-associated-uri header value.
*
* If unset, this configuration defaults to {@code null}.
* @param header The SIP P-associated-uri header value.
* @return this builder instance with the new configuration set.
*/
public @NonNull Builder setSipAssociatedUriHeader(@Nullable String header) {
mConfig.mAssociatedUriHeader = header;
return this;
}
/**
* @return A {@link SipDelegateConfiguration} instance with the optional configurations set.
*/
public @NonNull SipDelegateConfiguration build() {
return mConfig;
}
}
private final long mVersion;
private final int mTransportType;
private final InetSocketAddress mLocalAddress;
private final InetSocketAddress mSipServerAddress;
private boolean mIsSipCompactFormEnabled = false;
private boolean mIsSipKeepaliveEnabled = false;
private int mMaxUdpPayloadSize = -1;
private String mPublicUserIdentifier = null;
private String mPrivateUserIdentifier = null;
private String mHomeDomain = null;
private String mImei = null;
private Uri mGruu = null;
private String mSipAuthHeader = null;
private String mSipAuthNonce = null;
private String mServiceRouteHeader = null;
private String mPathHeader = null;
private String mUserAgentHeader = null;
private String mContactUserParam = null;
private String mPaniHeader = null;
private String mPlaniHeader = null;
private String mCniHeader = null;
private String mAssociatedUriHeader = null;
private IpSecConfiguration mIpSecConfiguration = null;
private InetSocketAddress mNatAddress = null;
private SipDelegateConfiguration(long version, int transportType,
InetSocketAddress localAddress, InetSocketAddress sipServerAddress) {
mVersion = version;
mTransportType = transportType;
mLocalAddress = localAddress;
mSipServerAddress = sipServerAddress;
}
private SipDelegateConfiguration(Parcel source) {
mVersion = source.readLong();
mTransportType = source.readInt();
mLocalAddress = readAddressFromParcel(source);
mSipServerAddress = readAddressFromParcel(source);
mIsSipCompactFormEnabled = source.readBoolean();
mIsSipKeepaliveEnabled = source.readBoolean();
mMaxUdpPayloadSize = source.readInt();
mPublicUserIdentifier = source.readString();
mPrivateUserIdentifier = source.readString();
mHomeDomain = source.readString();
mImei = source.readString();
mGruu = source.readParcelable(null, android.net.Uri.class);
mSipAuthHeader = source.readString();
mSipAuthNonce = source.readString();
mServiceRouteHeader = source.readString();
mPathHeader = source.readString();
mUserAgentHeader = source.readString();
mContactUserParam = source.readString();
mPaniHeader = source.readString();
mPlaniHeader = source.readString();
mCniHeader = source.readString();
mAssociatedUriHeader = source.readString();
boolean isIpsecConfigAvailable = source.readBoolean();
if (isIpsecConfigAvailable) {
mIpSecConfiguration = IpSecConfiguration.fromParcel(source);
}
boolean isNatConfigAvailable = source.readBoolean();
if (isNatConfigAvailable) {
mNatAddress = readAddressFromParcel(source);
}
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mVersion);
dest.writeInt(mTransportType);
writeAddressToParcel(mLocalAddress, dest);
writeAddressToParcel(mSipServerAddress, dest);
dest.writeBoolean(mIsSipCompactFormEnabled);
dest.writeBoolean(mIsSipKeepaliveEnabled);
dest.writeInt(mMaxUdpPayloadSize);
dest.writeString(mPublicUserIdentifier);
dest.writeString(mPrivateUserIdentifier);
dest.writeString(mHomeDomain);
dest.writeString(mImei);
dest.writeParcelable(mGruu, flags);
dest.writeString(mSipAuthHeader);
dest.writeString(mSipAuthNonce);
dest.writeString(mServiceRouteHeader);
dest.writeString(mPathHeader);
dest.writeString(mUserAgentHeader);
dest.writeString(mContactUserParam);
dest.writeString(mPaniHeader);
dest.writeString(mPlaniHeader);
dest.writeString(mCniHeader);
dest.writeString(mAssociatedUriHeader);
dest.writeBoolean(mIpSecConfiguration != null);
if (mIpSecConfiguration != null) {
mIpSecConfiguration.addToParcel(dest);
}
dest.writeBoolean(mNatAddress != null);
if (mNatAddress != null) {
writeAddressToParcel(mNatAddress, dest);
}
}
/**
* @return A copy of this instance with an incremented version.
* @hide
*/
public SipDelegateConfiguration copyAndIncrementVersion() {
SipDelegateConfiguration c = new SipDelegateConfiguration(getVersion() + 1, mTransportType,
mLocalAddress, mSipServerAddress);
c.mIsSipCompactFormEnabled = mIsSipCompactFormEnabled;
c.mIsSipKeepaliveEnabled = mIsSipKeepaliveEnabled;
c.mMaxUdpPayloadSize = mMaxUdpPayloadSize;
c.mIpSecConfiguration = mIpSecConfiguration;
c.mNatAddress = mNatAddress;
c.mPublicUserIdentifier = mPublicUserIdentifier;
c.mPrivateUserIdentifier = mPrivateUserIdentifier;
c.mHomeDomain = mHomeDomain;
c.mImei = mImei;
c.mGruu = mGruu;
c.mSipAuthHeader = mSipAuthHeader;
c.mSipAuthNonce = mSipAuthNonce;
c.mServiceRouteHeader = mServiceRouteHeader;
c.mPathHeader = mPathHeader;
c.mUserAgentHeader = mUserAgentHeader;
c.mContactUserParam = mContactUserParam;
c.mPaniHeader = mPaniHeader;
c.mPlaniHeader = mPlaniHeader;
c.mCniHeader = mCniHeader;
c.mAssociatedUriHeader = mAssociatedUriHeader;
return c;
}
/**
* An integer representing the version number of this SipDelegateImsConfiguration.
* {@link SipMessage}s that are created using this configuration will also have a this
* version number associated with them, which will allow the IMS service to validate that the
* {@link SipMessage} was using the latest configuration during creation and not a stale
* configuration due to race conditions between the configuration being updated and the RCS
* application not receiving the updated configuration before generating a new message.
*
* The version number should be a positive number that starts at 0 and increments sequentially
* as new {@link SipDelegateConfiguration} instances are created to update the IMS
* configuration state.
*
* @return the version number associated with this {@link SipDelegateConfiguration}.
*/
public @IntRange(from = 0) long getVersion() {
return mVersion;
}
/**
* @return The Transport type of the SIP delegate.
*/
public @TransportType int getTransportType() {
return mTransportType;
}
/**
* @return The local IP address and port used for SIP traffic.
*/
public @NonNull InetSocketAddress getLocalAddress() {
return mLocalAddress;
}
/**
* @return The default IP Address and port of the SIP server or P-CSCF used for SIP traffic.
*/
public @NonNull InetSocketAddress getSipServerAddress() {
return mSipServerAddress;
}
/**
* @return {@code true} if SIP compact form is enabled for the associated SIP Delegate,
* {@code false} if it is not.
*/
public boolean isSipCompactFormEnabled() {
return mIsSipCompactFormEnabled;
}
/**
* @return {@code true} if SIP keepalives are enabled for the associated SIP Delegate,
* {@code false} if it is not.
*/
public boolean isSipKeepaliveEnabled() {
return mIsSipKeepaliveEnabled;
}
/**
* @return The maximum SIP payload size in bytes for UDP or {code -1} if no value was set.
*/
public int getMaxUdpPayloadSizeBytes() {
return mMaxUdpPayloadSize;
}
/**
* @return The IMS public user identifier or {@code null} if it was not set.
*/
public @Nullable String getPublicUserIdentifier() {
return mPublicUserIdentifier;
}
/**
* @return The IMS private user identifier or {@code null} if it was not set.
*/
public @Nullable String getPrivateUserIdentifier() {
return mPrivateUserIdentifier;
}
/**
* @return The IMS home domain or {@code null} if it was not set.
*/
public @Nullable String getHomeDomain() {
return mHomeDomain;
}
/**
* get the IMEI of the associated device.
*
* Application can include the Instance-ID feature tag {@code "+sip.instance"} in the Contact
* header with a value of the device IMEI in the form {@code "urn:gsma:imei: