/** * Copyright 2014 Ricardo Padilha * * 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 net.dsys.snio.impl.codec; import java.nio.ByteBuffer; import java.util.zip.Checksum; import javax.annotation.Nonnull; import net.dsys.snio.api.codec.InvalidEncodingException; import net.dsys.snio.api.codec.InvalidMessageException; import net.dsys.snio.api.codec.MessageCodec; /** * Frame encoding with a 32-bit checksum at the end. Messages cannot be longer * than 65521 bytes to make sure that they will fit in an UDP datagram. * Thread-safety is guaranteed only between encoding and decoding, i.e., two * different threads can encode and decode at the same time, but two threads * cannot encode at the same time. * * @author Ricardo Padilha */ final class ChecksumCodec implements MessageCodec { private static final int INT_LENGTH = Integer.SIZE / Byte.SIZE; private final MessageCodec delegate; private final int headerLength; private final int bodyLength; private final int footerLength; private final int frameLength; private final Checksum encoder; private final Checksum decoder; private final byte[] encoderArray; private final byte[] decoderArray; ChecksumCodec(@Nonnull final MessageCodec codec, @Nonnull final Checksum encoder, @Nonnull final Checksum decoder) { if (codec == null) { throw new NullPointerException("codec == null"); } if (encoder == null) { throw new NullPointerException("encoder == null"); } if (decoder == null) { throw new NullPointerException("decoder == null"); } this.delegate = codec; this.headerLength = codec.getHeaderLength(); this.bodyLength = codec.getBodyLength(); this.footerLength = codec.getFooterLength() + INT_LENGTH; this.frameLength = codec.getFrameLength() + INT_LENGTH; this.encoder = encoder; this.decoder = decoder; this.encoderArray = new byte[codec.getFrameLength()]; this.decoderArray = new byte[codec.getFrameLength()]; } /** * {@inheritDoc} */ @Override public int getHeaderLength() { return headerLength; } /** * {@inheritDoc} */ @Override public int getBodyLength() { return bodyLength; } /** * {@inheritDoc} */ @Override public int getFooterLength() { return footerLength; } /** * {@inheritDoc} */ @Override public int getFrameLength() { return frameLength; } /** * {@inheritDoc} */ @Override public int getEncodedLength(final ByteBuffer in) { return delegate.getEncodedLength(in) + INT_LENGTH; } /** * {@inheritDoc} */ @Override public boolean isValid(final ByteBuffer out) { final int length = out.remaining(); return length > 0 && length <= bodyLength; } /** * {@inheritDoc} * @throws InvalidMessageException */ @Override public void put(final ByteBuffer in, final ByteBuffer out) throws InvalidMessageException { final int start = out.position(); delegate.put(in, out); final int end = out.position(); final int len = end - start; final int off; final byte[] array; if (out.hasArray()) { array = out.array(); off = out.arrayOffset() + start; } else { array = encoderArray; off = 0; // copy to a local array final int lim = out.limit(); out.position(start).limit(end); out.get(array, off, len); out.limit(lim).position(end); } encoder.reset(); encoder.update(array, off, len); final int checksum = (int) encoder.getValue(); out.putInt(checksum); } /** * {@inheritDoc} */ @Override public boolean hasNext(final ByteBuffer in) throws InvalidEncodingException { boolean hasNext = delegate.hasNext(in); if (hasNext) { final int rem = in.remaining(); hasNext = (rem >= getDecodedLength(in)); } return hasNext; } /** * {@inheritDoc} */ @Override public int getDecodedLength(final ByteBuffer in) { return delegate.getDecodedLength(in) + INT_LENGTH; } /** * Do not call this method unless {@link #hasNext(ByteBuffer)} has been * called before. * * {@inheritDoc} */ @Override public void get(final ByteBuffer in, final ByteBuffer out) throws InvalidEncodingException { final int start = in.position(); delegate.get(in, out); final int end = in.position(); final int len = end - start; final int off; final byte[] array; if (in.hasArray()) { array = in.array(); off = in.arrayOffset() + start; } else { array = decoderArray; off = 0; final int lim = in.limit(); in.position(start).limit(end); in.get(array, off, len); in.limit(lim).position(end); } decoder.reset(); decoder.update(array, off, len); final int calculated = (int) decoder.getValue(); final int received = in.getInt(); if (calculated != received) { throw new InvalidEncodingException("mismatching checksum"); } } /** * {@inheritDoc} */ @Override public void close() { delegate.close(); } /** * {@inheritDoc} */ @Override public String toString() { return "ChecksumCodec[" + encoder + ":" + decoder + "](" + delegate + ")"; } }