Java tutorial
/* * Copyright 2015 Sebastian Schmidl * * 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 sas.systems.imflux.packet.rtcp; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.util.CharsetUtil; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * A control packet of type BYE (goodbye): * <pre> * 0 1 2 3 bytes * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 bits * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * |V=2|P| RC | PT=BYE=203 | length | header * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | SSRC/CSRC | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * : ... : * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ * | length | reason for leaving ... (opt) * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * </pre> * @author <a:mailto="bruno.carvalho@wit-software.com" />Bruno de Carvalho</a> * @author <a href="https://github.com/CodeLionX">CodeLionX</a> * @see ControlPacket * @see SenderReportPacket * @see SourceDescriptionPacket * @see ReceiverReportPacket * @see AppDataPacket */ public class ByePacket extends ControlPacket { // internal vars -------------------------------------------------------------------------------------------------- private List<Long> ssrcList; private String reasonForLeaving; // constructors --------------------------------------------------------------------------------------------------- public ByePacket() { super(Type.BYE); } // public static methods ------------------------------------------------------------------------------------------ /** * Decodes a receiver report from a {@code ByteBuf}. This method is called by {@code ControlPacket.decode()}. * * @param buffer bytes, which still have to be decoded * @param innerBlocks number of reports in this packet * @param length remaining 32bit words * @return a new {@code ByePacket} containing all information from the {@code buffer} */ public static ByePacket decode(ByteBuf buffer, byte innerBlocks, int length) { ByePacket packet = new ByePacket(); int read = 0; for (int i = 0; i < innerBlocks; i++) { packet.addSsrc(buffer.readUnsignedInt()); read += 4; } // Length is written in 32bit words, not octet count. int lengthInOctets = length * 4; if (read < lengthInOctets) { byte[] reasonBytes = new byte[buffer.readUnsignedByte()]; buffer.readBytes(reasonBytes); packet.reasonForLeaving = new String(reasonBytes, CharsetUtil.UTF_8); read += (1 + reasonBytes.length); if (read < lengthInOctets) { // Skip remaining bytes (used for padding). This takes care of both the null termination bytes (padding // of the 'reason for leaving' string) and the packet padding bytes. buffer.skipBytes(lengthInOctets - read); } } return packet; } /** * Encodes a {@code ByePacket}. * * @param currentCompoundLength only needed for the padding if {@code fixedBlockSize > 0} * @param fixedBlockSize set this size if the packet should have a fixed size, otherwise 0 * @param packet the packet to be encoded * @return a {@code ByteBuf} containing the packet as bytes */ public static ByteBuf encode(int currentCompoundLength, int fixedBlockSize, ByePacket packet) { if ((currentCompoundLength < 0) || ((currentCompoundLength % 4) > 0)) { throw new IllegalArgumentException("Current compound length must be a non-negative multiple of 4"); } if ((fixedBlockSize < 0) || ((fixedBlockSize % 4) > 0)) { throw new IllegalArgumentException("Padding modulus must be a non-negative multiple of 4"); } int size = 4; ByteBuf buffer; if (packet.ssrcList != null) { size += packet.ssrcList.size() * 4; } byte[] reasonForLeavingBytes = null; int reasonForLeavingPadding = 0; if (packet.reasonForLeaving != null) { reasonForLeavingBytes = packet.reasonForLeaving.getBytes(CharsetUtil.UTF_8); if (reasonForLeavingBytes.length > 255) { throw new IllegalArgumentException( "Reason for leaving cannot exceed 255 bytes and this has " + reasonForLeavingBytes.length); } size += (1 + reasonForLeavingBytes.length); // 'reason for leaving' must be 32bit aligned, so extra null octets might be needed. reasonForLeavingPadding = 4 - ((1 + reasonForLeavingBytes.length) % 4); if (reasonForLeavingPadding == 4) { reasonForLeavingPadding = 0; } if (reasonForLeavingPadding > 0) { size += reasonForLeavingPadding; } } // If packet was configured to have padding, calculate padding and add it. int padding = 0; if (fixedBlockSize > 0) { // If padding modulus is > 0 then the padding is equal to: // (global size of the compound RTCP packet) mod (block size) // Block size alignment might be necessary for some encryption algorithms // RFC section 6.4.1 padding = fixedBlockSize - ((size + currentCompoundLength) % fixedBlockSize); if (padding == fixedBlockSize) { padding = 0; } } size += padding; // Allocate the buffer and write contents buffer = Unpooled.buffer(size); // First byte: Version (2b), Padding (1b), SSRC (chunks) count (5b) byte b = packet.getVersion().getByte(); if (padding > 0) { b |= 0x20; } if (packet.ssrcList != null) { b |= packet.ssrcList.size(); } buffer.writeByte(b); // Second byte: Packet Type buffer.writeByte(packet.type.getByte()); // Third byte: total length of the packet, in multiples of 4 bytes (32bit words) - 1 int sizeInOctets = (size / 4) - 1; buffer.writeShort(sizeInOctets); // Payload: ssrc list if (packet.ssrcList != null) { for (Long ssrc : packet.ssrcList) { buffer.writeInt(ssrc.intValue()); } } // If 'reason for leaving' was specified, add it. if (reasonForLeavingBytes != null) { buffer.writeByte(reasonForLeavingBytes.length); buffer.writeBytes(reasonForLeavingBytes); for (int i = 0; i < reasonForLeavingPadding; i++) { buffer.writeByte(0x00); } } if (padding > 0) { // Final bytes: padding for (int i = 0; i < (padding - 1); i++) { buffer.writeByte(0x00); } // Final byte: the amount of padding bytes that should be discarded. // Unless something's wrong, it will be a multiple of 4. buffer.writeByte(padding); } return buffer; } // ControlPacket -------------------------------------------------------------------------------------------------- /** * Encodes this {@code ByePacket}. * * @param currentCompoundLength only needed for the padding if {@code fixedBlockSize > 0} * @param fixedBlockSize set this size if the packet should have a fixed size, otherwise 0 * @return a {@code ByteBuf} containing the packet as bytes */ @Override public ByteBuf encode(int currentCompoundLength, int fixedBlockSize) { return encode(currentCompoundLength, fixedBlockSize, this); } /** * Encodes this {@code ByePacket}. * * @return a {@code ByteBuf} containing the packet as bytes */ @Override public ByteBuf encode() { return encode(0, 0, this); } // public methods ------------------------------------------------------------------------------------------------- public boolean addSsrc(long ssrc) { if ((ssrc < 0) || (ssrc > 0xffffffffL)) { throw new IllegalArgumentException("Valid range for SSRC is [0;0xffffffff]"); } if (this.ssrcList == null) { this.ssrcList = new ArrayList<Long>(); } return this.ssrcList.add(ssrc); } // getters & setters ---------------------------------------------------------------------------------------------- public List<Long> getSsrcList() { return Collections.unmodifiableList(this.ssrcList); } public void setSsrcList(List<Long> ssrcList) { this.ssrcList = new ArrayList<Long>(ssrcList.size()); for (Long ssrc : ssrcList) { // Validate each ssrc being added. this.addSsrc(ssrc); } } public String getReasonForLeaving() { return reasonForLeaving; } public void setReasonForLeaving(String reasonForLeaving) { this.reasonForLeaving = reasonForLeaving; } // low level overrides -------------------------------------------------------------------------------------------- @Override public String toString() { return new StringBuilder().append("ByePacket{").append("ssrcList=").append(this.ssrcList) .append(", reasonForLeaving='").append(reasonForLeaving).append('\'').append('}').toString(); } }