001/**
002 *
003 * Copyright the original author or authors
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.bytestreams.ibb.packet;
018
019import javax.xml.namespace.QName;
020
021import org.jivesoftware.smack.datatypes.UInt16;
022import org.jivesoftware.smack.packet.ExtensionElement;
023import org.jivesoftware.smack.packet.IQ.IQChildElementXmlStringBuilder;
024import org.jivesoftware.smack.util.Objects;
025import org.jivesoftware.smack.util.XmlStringBuilder;
026import org.jivesoftware.smack.util.stringencoder.Base64;
027
028/**
029 * Represents a chunk of data of an In-Band Bytestream within an IQ stanza or a
030 * message stanza.
031 *
032 * @author Henning Staib
033 */
034public class DataPacketExtension implements ExtensionElement {
035
036    /**
037     * The element name of the data stanza extension.
038     */
039    public static final String ELEMENT = "data";
040
041    /**
042     * The XMPP namespace of the In-Band Bytestream.
043     */
044    public static final String NAMESPACE = "http://jabber.org/protocol/ibb";
045
046    public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
047
048    /* unique session ID identifying this In-Band Bytestream */
049    private final String sessionID;
050
051    /* sequence of this packet in regard to the other data packets */
052    private final UInt16 seq;
053
054    /* the data contained in this packet */
055    private final String data;
056
057    private byte[] decodedData;
058
059    /**
060     * Creates a new In-Band Bytestream data packet.
061     *
062     * @param sessionID unique session ID identifying this In-Band Bytestream
063     * @param seq sequence of this stanza in regard to the other data packets
064     * @param data the base64 encoded data contained in this packet
065     * @throws IllegalArgumentException if seq is not within the range [0, 65535].
066     */
067    public DataPacketExtension(String sessionID, int seq, String data) {
068        this(sessionID, UInt16.from(seq), data);
069    }
070
071    /**
072     * Creates a new In-Band Bytestream data packet.
073     *
074     * @param sessionID unique session ID identifying this In-Band Bytestream
075     * @param seq sequence of this stanza in regard to the other data packets
076     * @param data the base64 encoded data contained in this packet
077     */
078    public DataPacketExtension(String sessionID, UInt16 seq, String data) {
079        if (sessionID == null || "".equals(sessionID)) {
080            throw new IllegalArgumentException("Session ID must not be null or empty");
081        }
082        if (data == null) {
083            throw new IllegalArgumentException("Data must not be null");
084        }
085        this.sessionID = sessionID;
086        this.seq = Objects.requireNonNull(seq);
087        this.data = data;
088    }
089
090    /**
091     * Returns the unique session ID identifying this In-Band Bytestream.
092     *
093     * @return the unique session ID identifying this In-Band Bytestream
094     */
095    public String getSessionID() {
096        return sessionID;
097    }
098
099    /**
100     * Returns the sequence of this stanza in regard to the other data packets.
101     *
102     * @return the sequence of this stanza in regard to the other data packets.
103     */
104    public UInt16 getSeq() {
105        return seq;
106    }
107
108    /**
109     * Returns the data contained in this packet.
110     *
111     * @return the data contained in this packet.
112     */
113    public String getData() {
114        return data;
115    }
116
117    /**
118     * Returns the decoded data or null if data could not be decoded.
119     * <p>
120     * The encoded data is invalid if it contains bad Base64 input characters or
121     * if it contains the pad ('=') character on a position other than the last
122     * character(s) of the data. See <a
123     * href="http://xmpp.org/extensions/xep-0047.html#sec">XEP-0047</a> Section
124     * 6.
125     *
126     * @return the decoded data
127     */
128    public byte[] getDecodedData() {
129        // return cached decoded data
130        if (this.decodedData != null) {
131            return this.decodedData;
132        }
133
134        // data must not contain the pad (=) other than end of data
135        if (data.matches(".*={1,2}+.+")) {
136            return null;
137        }
138
139        // decodeBase64 will return null if bad characters are included
140        this.decodedData = Base64.decode(data);
141        return this.decodedData;
142    }
143
144    @Override
145    public String getElementName() {
146        return ELEMENT;
147    }
148
149    @Override
150    public String getNamespace() {
151        return NAMESPACE;
152    }
153
154    @Override
155    public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
156        XmlStringBuilder xml = getIQChildElementBuilder(new IQChildElementXmlStringBuilder(this, enclosingNamespace));
157        xml.closeElement(this);
158        return xml;
159    }
160
161    protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
162        xml.attribute("seq", seq);
163        xml.attribute("sid", sessionID);
164        xml.rightAngleBracket();
165        xml.append(data);
166        return xml;
167    }
168}