io.pkts.packet.sip.SipMessage Maven / Gradle / Ivy
package io.pkts.packet.sip;
import io.pkts.buffer.Buffer;
import io.pkts.buffer.Buffers;
import io.pkts.packet.sip.address.SipURI;
import io.pkts.packet.sip.header.AddressParametersHeader;
import io.pkts.packet.sip.header.CSeqHeader;
import io.pkts.packet.sip.header.CallIdHeader;
import io.pkts.packet.sip.header.ContactHeader;
import io.pkts.packet.sip.header.ContentLengthHeader;
import io.pkts.packet.sip.header.ContentTypeHeader;
import io.pkts.packet.sip.header.ExpiresHeader;
import io.pkts.packet.sip.header.FromHeader;
import io.pkts.packet.sip.header.MaxForwardsHeader;
import io.pkts.packet.sip.header.RecordRouteHeader;
import io.pkts.packet.sip.header.RouteHeader;
import io.pkts.packet.sip.header.SipHeader;
import io.pkts.packet.sip.header.ToHeader;
import io.pkts.packet.sip.header.ViaHeader;
import io.pkts.packet.sip.impl.SipInitialLine;
import io.pkts.packet.sip.impl.SipParser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import static io.pkts.packet.sip.impl.PreConditions.assertNotEmpty;
import static io.pkts.packet.sip.impl.PreConditions.assertNotNull;
/**
* Packet representing a SIP message.
*
* @author [email protected]
*
*/
public interface SipMessage extends Cloneable {
String UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION = "Unable to parse out the method due to underlying IOException";
/**
* The first line of a sip message, which is either a request or a response
* line
*
* @return
*/
Buffer getInitialLine();
SipInitialLine initialLine();
/**
* Got tired of casting the {@link SipMessage} into a {@link SipRequest} so
* you can use this method instead. Just a short cut for:
*
*
* (SipRequest)sipMessage;
*
*
* @return this but casted into a {@link SipRequest}
* @throws ClassCastException
* in case this {@link SipMessage} is actually a
* {@link SipResponse}.
*/
default SipRequest toRequest() throws ClassCastException {
throw new ClassCastException("Unable to cast a " + this.getClass().getName() + " into a " + SipRequest.class.getName());
}
/**
* Got tired of casting the {@link SipMessage} into a {@link SipResponse} so
* you can use this method instead. Just a short cut for:
*
*
* (SipResponse)sipMessage;
*
*
* @return this but casted into a {@link SipResponse}
* @throws ClassCastException
* in case this {@link SipMessage} is actually a
* {@link SipResponse}.
*/
default SipResponse toResponse() throws ClassCastException {
throw new ClassCastException("Unable to cast a " + this.getClass().getName() + " into a " + SipResponse.class.getName());
}
/**
* Create a new response based on this {@link SipRequest}. If this
* {@link SipMessage} is not a {@link SipRequest} then a
* {@link ClassCastException} will be thrown. Only the mandatory headers
* from the {@link SipRequest} are copied. Those mandatory headers are:
*
* - {@link ToHeader}
* - {@link FromHeader}
* - {@link CallIdHeader}.
* - {@link CSeqHeader}
* - {@link ViaHeader}
*
*
* @param responseCode
* @return
* @throws SipParseException
* in case anything goes wrong when parsing out headers from the
* {@link SipRequest}
*/
default SipResponse.Builder createResponse(final int responseCode) throws SipParseException, ClassCastException {
return createResponse(responseCode, null);
}
default SipResponse.Builder createResponse(int responseCode, Buffer content) throws SipParseException, ClassCastException {
throw new ClassCastException("Unable to cast this SipMessage into a SipRequest");
}
/**
* Check whether this sip message is a response or not
*
* @return
*/
default boolean isResponse() {
return false;
}
/**
* Check whether this sip message is a request or not
*
* @return
*/
default boolean isRequest() {
return false;
}
default boolean isInviteRequest() {
return isRequest() && isInvite();
}
default boolean isByeRequest() {
return isRequest() && isBye();
}
default boolean isCancelRequest() {
return isRequest() && isCancel();
}
default boolean isRegisterRequest() {
return isRequest() && isRegister();
}
default boolean isOptionsRequest() {
return isRequest() && isOptions();
}
default boolean isInfoRequest() {
return isRequest() && isInfo();
}
default boolean isMessageRequest() {
return isRequest() && isMessage();
}
/**
* Convenience method for checking whether this an error response is >= 400.
*
* @return
*/
default boolean isError() {
return isResponse() && toResponse().isError();
}
/**
* Convenience method for checking whether this is a 1xx response or not.
*
* @return
*/
default boolean isProvisional() {
return isResponse() && toResponse().isProvisional();
}
/**
* Convenience method for checking whether this response is a final response, i.e. any response
* >= 200.
*
* @return
*/
default boolean isFinal() {
return isResponse() && toResponse().isFinal();
}
/**
* Convenience method for checking whether this is a 2xx response or not.
*
* @return
*/
default boolean isSuccess() {
return isResponse() && toResponse().isSuccess();
}
/**
* Convenience method for checking whether this is a 300 - 699. I.e. it's a final non-2xx response.
*
* @return
*/
default boolean isFinalNon2xx() {
return isFinal() && !isSuccess();
}
/**
* Convenience method for checking whether this is a 3xx response or not.
*
* @return
*/
default boolean isRedirect() {
return isResponse() && toResponse().isRedirect();
}
/**
* Convenience method for checking whether this is a 4xx response or not.
*
* @return
*/
default boolean isClientError() {
return isResponse() && toResponse().isClientError();
}
/**
* Convenience method for checking whether this is a 5xx response or not.
*
* @return
*/
default boolean isServerError() {
return isResponse() && toResponse().isServerError();
}
/**
* Convenience method for checking whether this is a 6xx response or not.
*
* @return
*/
default boolean isGlobalError() {
return isResponse() && toResponse().isGlobalError();
}
/**
* Convenience method for checking whether this is a 100 Trying response or
* not.
*
* @return
*/
default boolean is100Trying() {
return isResponse() && toResponse().is100Trying();
}
/**
* Convenience method for checking whether this is a 180 Ringing response or
* or a 183 Early Media response.
*
* @return true if this response is a 180 or a 183 response, false otherwise
*/
default boolean isRinging() {
return isResponse() && toResponse().isRinging();
}
/**
* Convenience method for checking whether this is a 480 Timeout response or
* not.
*
* @return
*/
default boolean isTimeout() {
return isResponse() && toResponse().isTimeout();
}
/**
* Get the content as a {@link Buffer}.
*
* @return
*/
Buffer getContent();
/**
* Checks whether this {@link SipMessage} is carrying anything in its
* message body.
*
* @return true if this {@link SipMessage} has a message body, false
* otherwise.
*/
boolean hasContent();
/**
* Get the method of this sip message
*
* @return
*/
Buffer getMethod() throws SipParseException;
/**
* Get the header as a buffer
*
* @param headerName
* the name of the header we wish to fetch
* @return the header as a {@link SipHeader} or null if not found
* @throws SipParseException
*/
Optional getHeader(Buffer headerName) throws SipParseException;
/**
* Same as {@link #getHeader(Buffers.wrap(keyParameter)}.
*
* @param headerName
* the name of the header we wish to fetch
* @return the header as a {@link SipHeader} or null if not found
* @throws SipParseException
*/
Optional getHeader(String headerName) throws SipParseException;
/**
* Get all headers with the given name.
*
* @param headerName
* @return a list of all headers or an empty list if none is found.
* @throws SipParseException
*/
List getHeaders(String headerName) throws SipParseException;
List getHeaders(Buffer headerName) throws SipParseException;
/**
* Convenience method for fetching the from-header
*
* @return the from header as a buffer
* @throws SipParseException
* TODO
*/
FromHeader getFromHeader() throws SipParseException;
/**
* Convenience method for fetching the to-header
*
* @return the to header as a buffer
*/
ToHeader getToHeader() throws SipParseException;
/**
* Get the top-most {@link ViaHeader} if present. If this is a request that
* has been sent then there should always be a {@link ViaHeader} present.
* However, you just created a {@link SipMessage} youself then this method
* may return null so please check for it.
*
* @return the top-most {@link ViaHeader} or null if there are no
* {@link ViaHeader}s on this message just yet.
* @throws SipParseException
*/
ViaHeader getViaHeader() throws SipParseException;
/**
* Get all the Via-headers in this {@link SipMessage}. If there are no
* {@link ViaHeader}s then an empty list will be returned.
*
* @return
* @throws SipParseException
*/
List getViaHeaders() throws SipParseException;
/**
*
* @return
* @throws SipParseException
*/
MaxForwardsHeader getMaxForwards() throws SipParseException;
/**
* Get the top-most {@link RecordRouteHeader} header if present.
*
* @return the top-most {@link RecordRouteHeader} header or null if there
* are no {@link RecordRouteHeader} headers found in this
* {@link SipMessage}.
* @throws SipParseException
*/
RecordRouteHeader getRecordRouteHeader() throws SipParseException;
/**
* Get all the RecordRoute-headers in this {@link SipMessage}. If there are
* no {@link RecordRouteHeader}s in this {@link SipMessage} then an empty
* list will be returned.
*
* @return
* @throws SipParseException
*/
List getRecordRouteHeaders() throws SipParseException;
/**
* Get the top-most {@link RouteHeader} header if present.
*
* @return the top-most {@link RouteHeader} header or null if there are no
* {@link RouteHeader} headers found in this {@link SipMessage}.
* @throws SipParseException
*/
RouteHeader getRouteHeader() throws SipParseException;
/**
* Get all the Route-headers in this {@link SipMessage}. If there are no
* {@link RouteHeader}s in this {@link SipMessage} then an empty list will
* be returned.
*
* @return
* @throws SipParseException
*/
List getRouteHeaders() throws SipParseException;
/**
* Get the {@link ExpiresHeader}
*
* @return
* @throws SipParseException
*/
ExpiresHeader getExpiresHeader() throws SipParseException;
/**
* Get the {@link ContactHeader}
*
* @return
* @throws SipParseException
*/
ContactHeader getContactHeader() throws SipParseException;
/**
* Get the {@link ContentTypeHeader} for this message. If there is no
* Content-Type header in this SIP message then null will be returned.
*
* @return the {@link ContentTypeHeader} or null if there is none.
* @throws SipParseException
*/
ContentTypeHeader getContentTypeHeader() throws SipParseException;
/**
* Return the content length. If the header isn't present then zero will
* be returned which DOES NOT mean that there isn't a body. Remember,
* this API doesn't enforce any rules regarding what headers you must/must not
* include in the message, this have to be enforced by business logic.
*
* @return the value of the Content-Length header if present or zero if
* there was no such header (or of course if the Content-Length header was
* present but actually had the value of zero)
*
* @throws SipParseException
*/
int getContentLength() throws SipParseException;
/**
* Convenience method for fetching the call-id-header
*
* @return the call-id header as a buffer
*/
CallIdHeader getCallIDHeader() throws SipParseException;
/**
* Convenience method for fetching the CSeq header
*
* @return
* @throws SipParseException
*/
CSeqHeader getCSeqHeader() throws SipParseException;
/**
* Convenience method for determining whether the method of this message is
* an INVITE or not.
*
* Note, this method only determined if it is an INVITE, which then could
* be either a request or a response, which you can check with {@link SipMessage#isRequest()} and
* {@link SipMessage#isResponse()}
*
* @return true if the method of this message is a INVITE, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isInvite() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'I' && m.getByte(1) == 'N' && m.getByte(2) == 'V' && m.getByte(3) == 'I'
&& m.getByte(4) == 'T' && m.getByte(5) == 'E';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is an REGISTER or not.
*
* @return true if the method of this message is a REGISTER, false otherwise.
* @throws SipParseException in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isRegister() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'R' && m.getByte(1) == 'E' && m.getByte(2) == 'G' && m.getByte(3) == 'I'
&& m.getByte(4) == 'S' && m.getByte(5) == 'T' && m.getByte(6) == 'E' && m.getByte(7) == 'R';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is a BYE or not. Hence,
* this is NOT to the method to determine whether this is a BYE Request or not!
*
* @return true if the method of this message is a BYE, false otherwise.
* @throws SipParseException in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isBye() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'B' && m.getByte(1) == 'Y' && m.getByte(2) == 'E';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is
* an ACK or not. Hence, this is NOT to the method to determine whether this
* is an ACK Request or not!
*
* @return true if the method of this message is a ACK, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isAck() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'A' && m.getByte(1) == 'C' && m.getByte(2) == 'K';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is
* a CANCEL or not
*
* @return true if the method of this message is a CANCEL, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isCancel() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'C' && m.getByte(1) == 'A' && m.getByte(2) == 'N' && m.getByte(3) == 'C'
&& m.getByte(4) == 'E' && m.getByte(5) == 'L';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is
* a OPTIONS or not. Hence, this is NOT to the method to determine whether
* this is an OPTIONS Request or not!
*
* @return true if the method of this message is a OPTIONS, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isOptions() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'O' && m.getByte(1) == 'P' && m.getByte(2) == 'T' && m.getByte(3) == 'I'
&& m.getByte(4) == 'O' && m.getByte(5) == 'N' && m.getByte(6) == 'S';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is
* a MESSAGE or not. Hence, this is NOT to the method to determine whether
* this is an MESSAGE Request or not!
*
* @return true if the method of this message is a MESSAGE, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isMessage() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'M' && m.getByte(1) == 'E' && m.getByte(2) == 'S' && m.getByte(3) == 'S'
&& m.getByte(4) == 'A' && m.getByte(5) == 'G' && m.getByte(6) == 'E';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Convenience method for determining whether the method of this message is
* a INFO or not. Hence, this is NOT to the method to determine whether this
* is an INFO Request or not!
*
* @return true if the method of this message is a INFO, false otherwise.
* @throws SipParseException
* in case the method could not be parsed out of the underlying
* buffer.
*/
default boolean isInfo() throws SipParseException {
final Buffer m = getMethod();
try {
return m.getByte(0) == 'I' && m.getByte(1) == 'N' && m.getByte(2) == 'F' && m.getByte(3) == 'O';
} catch (final IOException e) {
throw new SipParseException(0, UNABLE_TO_PARSE_OUT_THE_METHOD_DUE_TO_UNDERLYING_IO_EXCEPTION, e);
}
}
/**
* Checks whether or not this request is considered to be an "initial"
* request, i.e., a request that does not go within a dialog.
*
* @return
* @throws SipParseException
*/
boolean isInitial() throws SipParseException;
default boolean isSubsequent() throws SipParseException {
return !isInitial();
}
/**
*
*
* - ruri sip version - checks if the SIP version in the request URI is
* supported, currently only 2.0.
* - ruri scheme - checks if the URI scheme of the request URI is
* supported (sip[s]|tel[s]) by SIP-router.
* - required headers - checks if the minimum set of required headers to,
* from, cseq, callid and via is present in the request.
* - via sip version - not working because parser fails already when
* another version then 2.0 is present.
* - via protocol - not working because parser fails already if an
* unsupported transport is present.
* - cseq method - checks if the method from the cseq header is equal to
* the request method.
* - cseq value - checks if the number in the cseq header is a valid
* unsigned integer.
* - content length - checks if the size of the body matches with the
* value from the content length header.
* - expires value - checks if the value of the expires header is a valid
* unsigned integer.
* - proxy require - checks if all items of the proxy require header are
* present in the list of the extensions from the module parameter
* proxy_require.
*
* - parse uri's - checks if the specified URIs are present and parseable
* by the SIP-router parsers
* - digest credentials - Check all instances of digest credentials in a
* message. The test checks whether there are all required digest parameters
* and have meaningful values.
*
*
*
*
* This list is taken from Kamailio.org
*
*
*/
void verify();
/**
* Get the {@link Buffer} that is representing this {@link SipMessage}.
*
* @return
*/
Buffer toBuffer();
/**
* Perform a deep clone of this SipMessage.
*
* @return
*/
SipMessage clone();
/**
* Frame the supplied buffer into a {@link SipMessage}. No deep analysis of the message will be
* performed so there is no guarantee that this {@link SipMessage} is actually a well formed
* message.
*
* @param buffer
* @return the framed {@link SipMessage}
*/
static SipMessage frame(final Buffer buffer) throws SipParseException, IOException {
assertNotNull(buffer);
return SipParser.frame(buffer);
}
/**
*
* @param buffer
* @return
* @throws IOException
*/
static SipMessage frame(final String buffer) throws SipParseException, IOException {
assertNotEmpty(buffer, "Buffer cannot be null or the empty string");
return SipParser.frame(Buffers.wrap(buffer));
}
static SipMessage frame(final byte[] buffer) throws SipParseException, IOException {
assertNotNull(buffer, "Byte-array cannot be null");
return SipParser.frame(Buffers.wrap(buffer));
}
default int countNoOfHeaders() {
return getAllHeaders().size();
}
Map> getHeaderValues();
default List getAllHeaders() {
// TODO: can't be a default implementation of this. Just doing this while refactoring...
return new ArrayList<>();
}
Builder extends SipMessage> copy();
/**
* Whenever you create a new {@link SipMessage} you will end up with a {@link Builder}.
* The pattern of the {@link SipMessage} builders is that you can specify the various
* headers, request-uri, body etc through the withXXX-methods and then upon build time,
* the builder will call out to any registered functions (as registered through the onXXX-methods)
* allowing the application to make any last minute changes.
*
* In a real SIP stack there are headers within a {@link SipMessage} that typically needs to be
* manipulated before being sent out over the network. One such header is the {@link ViaHeader} where
* the transport layer typically has the responsibility to fill out the ip and port from which the
* message was sent as well as specify the transport used.
*
* The idea is as follows: everything within this SIP library is immutable, which includes {@link SipRequest}
* and {@link SipResponse}s but if you build a stack, the stack may actually need to change headers
* before creating the "final" version of the {@link SipMessage} which is then sent out over the network.
* The typical use case is of course the {@link ViaHeader} where it typically isn't known by the application
* which protocol, or interface of the stack will be used. This is only known at the time the message
* is about to be sent out and will (should) be filled out by the transport layer. Therefore, if you build
* a stack you probably want to pass down a {@link Builder} to be "sent" all the way down to the transport layer
* which will
*
* All callback as registered through the various onXXXX-methods allow for multiple callbacks to be registered.
* They are called in reverse order from the order of registration.
*
* TODO: don't think I will do this anymore. If you add the same header via an of the three methods then
* TODO: that header will be included three times. I.e., if you call withHeaderBuilder and withHeader and
* TODO: the same header also exists in the template then it will be included that many times.
* Order of precedence where the top one "wins" over the others:
*
* - Any header added as a builder object will ALWAYS take precedence
* - Any header added through a withXXX-method
* - Any header copied from a "template"
*
*
* I.e., if you e.g. have added a from-header builder object
* through {@link Builder#withFromHeader(AddressParametersHeader.Builder)}
* method then that builder will be used even if this builder is based off
* another {@link SipMessage} (which then serves as a "template")
*
* TODO: add and/or clarify how headers are treated in general, i.e.:
*
* A header can be added to the final {@link SipMessage} that is being built via 1 out of 3 ways:
*
* - Either via explicitly calling withXXXBuilder to add a builder object for that header
* - Or by explicitly calling withXXXHeader
* - Or by copying an existing header from the {@link SipMessage} we are using as a template
* for constructing this new sip message.
*
*
* No matter how a header is added to the final message (via one of the three ways described above)
* you will be given the opportunity to change the value of the header by registering a function
* for manipulating the header just before it gets added. You do so through two methods:
*
* - {@link io.pkts.packet.sip.SipMessage.Builder#onHeader(Function)}
* - or {@link io.pkts.packet.sip.SipMessage.Builder#onHeaderBuilder(Consumer)}
*
*
* The first method is called when a header was added without a builder already created from it,
* which is the case when a header is copied from the template or when you called onXXXHeader(header).
* The reason for this is unless you actually want to change it, we don't want to waste time on
* constructing a builder that you are not going to use. However, if you added the header
* as a builder object then we will of coruse use that builder.
*
*/
interface Builder {
default boolean isSipRequestBuilder() {
return false;
}
default boolean isSipResponseBuilder() {
return false;
}
default SipMessage.Builder toSipRequestBuilder() {
throw new ClassCastException("Cannot cast " + getClass().getName() + " into a SipRequest builder");
}
default SipMessage.Builder toSipResponseBuilder() {
throw new ClassCastException("Cannot cast " + getClass().getName() + " into a SipResponse builder");
}
/**
* By default, the following headers will automatically be generated if not
* explicitly provided (note: there is a slight difference between request/response):
*
*
* - {@link ToHeader} - the request-uri will be used to construct the to-header
* in the case of a request. For a response you have to supply it
* - {@link CSeqHeader} - a new CSeq header will be added where the
* method is the same as this message and the sequence number is set to 1
* - {@link CallIdHeader} - a new random call-id will be added
* - {@link MaxForwardsHeader} - if we are building a request, a max forwards of 70 will be added
* - {@link ContentLengthHeader} - Will be added if there is a body
* on the message and the length set to the correct length.
*
*
* but if you don't want that, simply call this method and all the defaults
* of this builder will be suspended. Of course, if you wish to actually
* construct a valid {@link SipMessage} you are then responsible for adding
* the mandatory headers to this builder (unless you don't care of course
* because perhaps you are building a test tool meant to torture test
* a SIP server).
*
* @return
*/
Builder withNoDefaults();
/**
* A header can be added to the new {@link SipMessage} in two ways,
* either by being copied from the {@link SipMessage} used
* as a template, or the header can be explicitly added through one of the withXXX-methods.
* Those headers that are copied from the template are subject to filtering and
* those headers that have been explicitly added are not (since if you
* did add them it is assumed you actually want to include them. If not, why add them
* in the first place?)
*
* Any header, which includes those copied headers that "survived" the filtering
* step, can be manipulated before it is added to the new SIP message by registering
* a function with the method {@link Builder#onHeader(Function)}.
*
* Note, this API allows you to filter out mandatory headers, such as the {@link ContactHeader} etc,
* and of course, if you are building a real SIP stack you need to include the mandatory
* headers but the reason why there are no restrictions imposed by this library is because
* perhaps you want to build a tool that actually sends bad {@link SipMessage}s? You should be
* able to do so and therefore, there are no restrictions on how you create your messages.
* It is up to your stack/application to enforce any rules you see fit.
*
* @param filter
* @throws IllegalStateException in case a filter already had been registered with
* this builder.
*/
// Builder filter(Predicate filter) throws IllegalStateException;
/**
* Whenever a header is about to be pushed onto the new {@link SipMessage}
* you have a chance to change the value of that header. You do so
* by registering a function that accepts a {@link SipHeader} as an argument and that
* returns a {@link SipHeader}, which is the header that will be pushed onto the new
* {@link SipMessage}. If you do not want to include the header, then simply return
* null and the header will be dropped.
*
* If you wish to leave the header un-touched, then simply return it has is.
*
* Also note that the following headers have explicit "on" methods (they are considered
* to be "system" headers):
*
*
* - {@link FromHeader}
* - {@link ToHeader}
* - {@link ContactHeader}
* - {@link ViaHeader}
* - {@link RouteHeader}
* - {@link RecordRouteHeader}
* - {@link MaxForwardsHeader}
* - {@link CSeqHeader}
*
*
* The reason is simply because these are typically manipulated before
* copying them over to a new request or response (e.g., Max Forwards is decremented,
* CSeq may increase etc) and therefore it makes life easier if those headers are
* "down casted" to their specific types.
*
*
* @param f
* @return
* @throws IllegalStateException in case a function already had been registered with
* this builder.
*/
Builder onHeader(Function f) throws IllegalStateException;
/**
* Adds the header to the list of headers already specified within this builder.
* The header will be added last to the list of headers. Any already existing
* headers with the same name will be preserved as is.
*
* If there are any headers with the same name as part of the {@link SipMessage}
* used as a template, then those headers will
*
* TODO: this is essentially an "add header" so should it be called that?
* TODO: and then should there be a set version? Just goes bad with a fluent
* TODO: naming.
* @param header
* @return
*/
Builder withHeader(SipHeader header);
Builder withHeaders(List headers);
/**
* Push the header to be the first on the list of existing headers already
* added to this builder.
*
* TODO: naming
* @return
*/
Builder withPushHeader(SipHeader header);
Builder onFromHeader(Consumer> f);
/**
* Set the {@link FromHeader} to be used by the new {@link SipMessage}. If there already
* is a {@link FromHeader} present, either through a template or because this method
* has been called previously, that value will be overwritten.
*
* @param from
* @return
*/
Builder withFromHeader(FromHeader from);
Builder withFromHeader(String from);
Builder onToHeader(Consumer> f);
Builder withToHeader(ToHeader to);
Builder withToHeader(String to);
Builder onContactHeader(Consumer> f);
Builder withContactHeader(ContactHeader contact);
Builder onCSeqHeader(Consumer f);
Builder withCSeqHeader(CSeqHeader cseq);
Builder onMaxForwardsHeader(Consumer f);
Builder withMaxForwardsHeader(MaxForwardsHeader maxForwards);
Builder withCallIdHeader(CallIdHeader callID);
/**
* Called when the top-most Route header is processed. If there are
* more than one Route header present, the other ones will be
* processed via the {@link io.pkts.packet.sip.SipMessage.Builder#onRouteHeader(Consumer)}
*
* @param f
* @return
*/
Builder onTopMostRouteHeader(Consumer> f);
/**
* Called when a Route header is processed (except for the top-most one,
* then {@link io.pkts.packet.sip.SipMessage.Builder#onTopMostRouteHeader(Consumer)}
* is called instead)
*
* @param f
* @return
*/
Builder onRouteHeader(Consumer> f);
/**
* Set a Router header to be used on the message that is being built. This will
* replace any previously set Route headers. If you wish to add a number of
* Route headers, use {@link io.pkts.packet.sip.SipMessage.Builder#withRouteHeaders(RouteHeader...)}.
* If you want to push a Route header to a potentially already existing list
* of Record Route headers, then use {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostRouteHeader(RouteHeader)}
*
* @param route
* @return
*/
Builder withRouteHeader(RouteHeader route);
/**
* Set a list of Route headers. Any previously Route headers
* will be replaced by this list.
*
* @param routes
* @return
*/
Builder withRouteHeaders(RouteHeader ... routes);
Builder withRouteHeaders(List routes);
/**
* Push the given Route header to the top of the potential list of existing
* Route headers.
*
* @param route
* @return
*/
Builder withTopMostRouteHeader(RouteHeader route);
/**
* Pop the top-most route. Note, if you e.g. add a {@link RouteHeader} via the method
* {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostRouteHeader(RouteHeader)} followed
* by this method, then the route you just added will be removed again. Order is important!
*
* Note: if you actually wanted to know the value of that {@link RouteHeader} then you should
* really have checked it on the {@link SipMessage} you received and not on the builder
* object.
*
* @return
*/
Builder withPoppedRoute();
/**
* Sometimes you may want to just wipe out all the potential {@link RouteHeader}s
* that e.g. were automatically copied from another {@link SipRequest} that was
* used as a template. A common scenario is that you are building a B2BUA acting
* as the border police to your network and you simply cannot trust incoming requests
* and as such, you should not honor externally pushed routes, since if you did, an
* attacker could by-pass your next hop by forcing the request to go to somewhere else.
*
* @return
*/
Builder withNoRoutes();
// TODO: CSeq, MaxForwards
/**
* Called when the top-most Record Route header is processed. If there are
* more than one Record Route header present, the other ones will be
* processed via the {@link io.pkts.packet.sip.SipMessage.Builder#onRecordRouteHeader(Consumer)}
*
* @param f
* @return
*/
Builder onTopMostRecordRouteHeader(Consumer> f);
/**
* Called when a Record-Route header is processed (except for the top-most one,
* then {@link io.pkts.packet.sip.SipMessage.Builder#onTopMostRecordRouteHeader(Consumer)}
* is called instead)
*
* @param f
* @return
*/
Builder onRecordRouteHeader(Consumer> f);
/**
* Set a Record Router header to be used on the message that is being built. This will
* replace any previously set Record Route headers. If you wish to add a number of
* Record Route headers, use {@link io.pkts.packet.sip.SipMessage.Builder#withRecordRouteHeaders(RecordRouteHeader...)}.
* If you want to push a Record Route header to a potentially already existing list
* of Record Route headers, then use
*
* @param recordRoute
* @return
*/
Builder withRecordRouteHeader(RecordRouteHeader recordRoute);
/**
* Set a list of Record Route headers. Any previously Record Route headers
* will be replaced by this list.
*
* @param recordRoute
* @return
*/
Builder withRecordRouteHeaders(RecordRouteHeader ... recordRoute);
Builder withRecordRouteHeaders(List recordRoute);
/**
* Push the given Record Route header to the top of the potential list of existing
* Record Route headers.
*
* @param recordRoute
* @return
*/
Builder withTopMostRecordRouteHeader(RecordRouteHeader recordRoute);
/**
*
* @param f
* @return
*/
Builder onRequestURI(Function f);
// Builder withTopMostVia(ViaHeader.Builder via);
// Builder pushVia(ViaHeader.Builder via);
// Builder withPushedVia(ViaHeader.Builder via);
// Builder pushVia(ViaHeader via);
// void onTopMostVia(Function f);
/**
* Called when the top-most Via header is processed. If there are
* more than one Via header present, the other ones will be
* processed via the {@link io.pkts.packet.sip.SipMessage.Builder#onViaHeader(Consumer)}
* consumer
*
* @param f
* @return
*/
Builder onTopMostViaHeader(Consumer f);
/**
* Called when a Via header is processed and the first argument is the
* index of the Via being processed. The top-most Via header will NEVER
* be passed to this registered function but rather to the one explicitly
* meant for processing the top-most via (see {@link Builder#onTopMostViaHeader(Consumer)}.
*
* I.e., Let's say you have the following message (request or response, same same):
*
*
* ...
* Via: SIP/2.0/TCP 12.13.14.15;branch=z9hG4bK-asdf
* Via: SIP/2.0/UDP 60.61.62.63;branch=z9hG4bK-1234
* Via: SIP/2.0/UDP 96.97.98.99;branch=z9hG4bK-wxyz
* ...
*
*
*
* - When the top-most via header (12.13.14.15) is processed, the function registered with
* {@link Builder#onTopMostViaHeader(Consumer)} will be called.
*
* - When the second via is processed (60.61.62.63), the function registered with
* {@link Builder#onViaHeader(BiConsumer)} will be called where the index will
* be '1' since this is the second via on the list and we of course start counting
* at zero.
*
* - When the third via is processed (96.97.98.99), the function registered with
* {@link Builder#onViaHeader(BiConsumer)} will be called where the index will
* be '2' since this is the third via on the list and so on...
*
*
*
* @param f
* @return
*/
Builder onViaHeader(BiConsumer f);
/**
* Add a Via header to be used on the message that is being built. This will
* replace any previously set Via headers. If you wish to add a number of
* Via headers, use {@link io.pkts.packet.sip.SipMessage.Builder#withViaHeaders(ViaHeader...)}.
* If you want to push a Via header to a potentially already existing list
* of Via headers, then use {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostViaHeader(ViaHeader)}.
*
* @param via
* @return
*/
Builder withViaHeader(ViaHeader via);
/**
* Set a list of Via headers. Any previously Via headers
* will be replaced by this list.
*
* @param vias
* @return
*/
Builder withViaHeaders(ViaHeader ... vias);
/**
* Set a list of Via headers. Any previously Via headers
* will be replaced by this list.
*
* @param vias
* @return
*/
Builder withViaHeaders(List vias);
/**
* Push the given Via header to the top of the potential list of existing
* Via headers.
*
* @param via
* @return
*/
Builder withTopMostViaHeader(ViaHeader via);
/**
* Typically the {@link ViaHeader} will have to be filled out by the stack at some
* later point, which is when the message is about to be sent, so when you create
* the message you don't have all the details just yet. However, just to create
* a new Via with bogus information only to be re-written later by registering
* a function with {@link Builder#onTopMostViaHeader(Consumer)} is rather silly
* so therefore you can just indicate that you want a new top-most via header
* but you will fill out all details later.
*
* NOTE: if you do NOT register a function to handle this "empty" Via-header
* through the method {@link Builder#onTopMostViaHeader(Consumer)} things will
* blow up later with you try to build this message.
*
* @return
*/
Builder withTopMostViaHeader();
/**
* Pop the top-most via. Note, if you e.g. add a {@link ViaHeader} through the method
* {@link io.pkts.packet.sip.SipMessage.Builder#withTopMostViaHeader(ViaHeader)} followed
* by this method, then the Via you just added will be removed again. Order is important!
*
* Note: if you actually wanted to know the value of that {@link ViaHeader} then you should
* really have checked it on the {@link SipMessage} you received and not on the builder
* object.
*
* @return
*/
Builder withPoppedVia();
Builder withBody(Buffer body);
T build();
/**
* After the {@link SipMessage} has been fully built and created the "end result"
* will be conveyed to the registered function. It is utterly important
* that the function returns as quickly as possible since the build method
* will not be able to return until the call to this function has been completed.
*
* @param f
*/
Builder onCommit(Consumer f);
}
}