CodecBuffer.java 18 KB
Newer Older
filatov's avatar
filatov committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
/**
 * @author      ETSI / STF462 / Alexandre Berge
 * @version     $URL$
 *              $Id$
 */
package org.etsi.ttcn.codec;

import java.util.Map;
import java.util.TreeMap;

import org.etsi.ttcn.common.ByteHelper;

public class CodecBuffer {

    /**
     * Raw byte storage array. Last byte may be a "partial byte", i.e. some of its bits may not be significants
     * @see     bits
     */
    protected byte[] buffer;
        
    /**
     * Number of significant bits in the "partial byte"
     * Significant bits are always stored "left-aligned" (i.e. MSBs) in the "partial byte"
     * @see     buffer 
     */
    protected int bits;
        
    /**
     * Marker storage
     */
    protected Map<String, Marker> markers = new TreeMap<String, Marker>();

    /**
     * Some useful byte masks
     */
    private byte[] masks = new byte[]{(byte)0x00, (byte)0x80, (byte)0xC0, (byte)0xE0, (byte)0xF0, (byte)0xF8, (byte)0xFC, (byte)0xFE};
    
    /**
     * Some useful byte masks
     */
    private byte[] nomasks = new byte[]{(byte)0xFF, (byte)0x7F, (byte)0x3F, (byte)0x1F, (byte)0x0F, (byte)0x07, (byte)0x03, (byte)0x01};

    /**
     * Main constructor. Initialises an empty buffer
     */
    public CodecBuffer() {
        bits = 0;
        buffer = new byte[]{};
    }

    /**
     * Constructor. Initialises the buffer using provided byte array
     * @param bytes     Initial content of the buffer
     */
    public CodecBuffer(byte[] bytes) {
        bits = 0;
        buffer = bytes.clone();
    }

    /**
     * Overwrite content of current buffer using data of newBuffer
     * @param newBuffer CodecBuffer containing new data
     */
    public void overwriteWith(CodecBuffer newBuffer) {
        
        bits = newBuffer.bits;
        buffer = newBuffer.buffer.clone();
        markers = new TreeMap<String, Marker>(newBuffer.markers);       
    }
    
    /**
     * Retrieves the number of significant bits in the buffer.
     * Warning: getNbBytes() != (getNbBits() * 8) 
     * @return  The number of significant bits in the buffer
     * @see     getNbBytes()
     */
    public int getNbBits() {

        if(bits > 0) {
            return (buffer.length - 1) * 8 + bits;
        }
        return buffer.length * 8;
    }

    /**
     * Retrieves the number of bytes used to store the buffer.
     * Warning: getNbBytes() != (getNbBits() * 8) 
     * @return  The number of bytes used to store the buffer
     * @see     getNbBits()
     */
    public int getNbBytes() {
        return buffer.length;
    }

    /**
     * Concatenates current CodecBuffer and the CodecBuffer passed as parameter.
     * Markers of buffer2 are preserved and integrated into the current CodecBuffer.
     * @param buffer2   The CodecBuffer to be appended
     */
    public void append(CodecBuffer buffer2) {
        
        // copy buffer content
        int nbBits = getNbBits();
        if(buffer2.getNbBits() > 0) {
            appendBits(buffer2.getBits(), buffer2.getNbBits());
        }

        // integrate markers
        for(Map.Entry<String, Marker> entry : buffer2.markers.entrySet()) {
            String key = entry.getKey();
            Marker marker = entry.getValue();
            marker.move(nbBits);
            markers.put(key, marker);
        }
    }

    /**
     * Associates a new marker to current CodecBuffer.
     * Inserting marker with name identical to a previously inserted one will overwrite it.
     * @param key       Name of the marker
     * @param pos       Position of the marker relative to the current position
     * @param callback  Optional callback object to be executed later, or null
     * @see             runCallbacks()
     */
    public void setMarker(String key, int pos, IMarkerCallback callback) {
        markers.put(key, new Marker(pos, callback));
    }

    /**
     * Executes all the callbacks associated to current CodecBuffer's markers
     * @see     setMarkers()
     */
    public void runCallbacks() {
        System.out.println("Running callbacks...");
        for(Map.Entry<String, Marker> entry : markers.entrySet()) {
            String key = entry.getKey();
            System.out.println("Running callback: " + key);
            Marker marker = entry.getValue();
            IMarkerCallback callback = marker.getCallback();
            if(callback != null) {
                CodecBuffer Left = getBuffer(0, marker.getPos());
                CodecBuffer Right = getBuffer(marker.getPos(), getNbBits() - marker.getPos());

                callback.run(key, Left, Right);

                // Overwrite self with Left+Right
                // TODO: take care if Right or Left have change to much.
                Left.append(Right);
                buffer = Left.buffer;
                bits = Left.bits;
            }
        }	
    }

    /**
     * Creates a new CodecBuffer from current CodecBuffer's content
     * Markers of current CodecBuffer are preserved and integrated into the new CodecBuffer.
     * @param start     Start point. Bit offset from current position
     * @param length    Amount of bits to be copied
     * @return          The new CodecBuffer
     */
    public CodecBuffer getBuffer(int start, int length) {
        // TODO: check param validity

        CodecBuffer res = new CodecBuffer();
        res.setBits(getBits(start, length), length);

        for(Map.Entry<String, Marker> entry : markers.entrySet()) {
            String key = entry.getKey();
            Marker marker = entry.getValue();
            int pos = marker.getPos();
            if(pos >= start && pos < (start + length)) {
                res.setMarker(key, pos - start, marker.getCallback());
            }
        }	
        return res;
    }

    /**
     * Appends some raw bytes at the end of the current CodecBuffer
     * @param origBytes     Bytes to be appended
     */
    public void appendBytes(byte[] origBytes) {

        byte[] bytes = origBytes.clone();

        int lastByte = buffer.length - 1;
        buffer = ByteHelper.concat(buffer, bytes);

        if(bits != 0) {
            for(int i=0; i < bytes.length; i++) {
                buffer[lastByte] &= masks[bits];
                buffer[lastByte] |= ((bytes[i] >>> bits) & nomasks[bits]);
                lastByte++;
                buffer[lastByte] = (byte)(buffer[lastByte] << (8 - bits));
            }

            buffer[buffer.length - 1] &= masks[bits];
        }
    }

    /**
     * Appends some raw bits at the end of the current CodecBuffer
     * @param origBytes     Byte array used to store the bits to be appended.   
     *                      It MUST be right-aligned. First byte (origBytes[0]) may be a 
     *                      partial byte if 'nbBits' is not a multiple of 8. 
     *                      In this case MSBs of this byte are ignored and not copied
     * @param nbBits        Number of significant bits in 'origBytes'
     */
    public void appendBits(byte[] origBytes, int nbBits) {

        byte[] bytes = origBytes.clone();
        int rbits = nbBits % 8;
        int nbBytes = nbBits / 8 + ((rbits > 0)?1:0);
        int lastByte = buffer.length - 1;

        // Left-align received bytes
        if(rbits !=0) {
            int i;
            for(i=(bytes.length - nbBytes); i < (nbBytes - 1); i++) {
                bytes[i] = (byte)(bytes[i] << (8 - rbits));
                bytes[i] |= ((bytes[i+1] >>> rbits) & nomasks[rbits]);
            }
            bytes[i] = (byte)(bytes[i] << (8 - rbits));
            bytes[i] &= masks[rbits];
        }

        buffer = ByteHelper.concat(buffer, ByteHelper.extract(bytes, (bytes.length - nbBytes), nbBytes));
        if(bits != 0) {
            int i;
            for(i=lastByte; i < (lastByte + nbBytes); i++) {
                buffer[i] &= masks[bits];
                buffer[i] |= ((buffer[i+1] >>> bits) & nomasks[bits]);
                buffer[i+1] = (byte)(buffer[i+1] << (8 - bits));
            }
            buffer[i] &= masks[bits];

            if((rbits > 0) && (rbits + bits <= 8)) {
                // Remove trailing byte (garbage)
                buffer = ByteHelper.extract(buffer, 0, buffer.length - 1);
            }

        }

        bits += nbBits;
        bits %= 8;
    }

    /**
     * Overwrite the content of CodecBuffer using the provided bytes
     * @param bytes     New content of the CodecBuffer
     * @see             setBits()
     */
    public void setBytes(byte[] bytes) {
        buffer = bytes.clone();
        bits = 0;
        markers.clear();
    }

    /**
     * Overwrite the content of CodecBuffer using the provided bits
     * @param bytes     Byte array used to store the bits to be used.   
     *                      It MUST be right-aligned. First byte (origBytes[0]) may be a 
     *                      partial byte if 'nbBits' is not a multiple of 8. 
     *                      In this case MSBs of this byte are ignored and not copied
     * @param nbBits        Number of significant bits in 'bytes'
     * @see             setBytes()
     */
    public void setBits(byte[] bytes, int nbBits) {

        if(nbBits == 0) {
            bits = 0;
            buffer = new byte[]{};
        }
        else {
            int i;
            int rbits = nbBits % 8;
            int nbBytes = nbBits / 8 + ((rbits > 0)?1:0);
            bits = rbits;
            buffer = bytes.clone();

            if(bits !=0) {
                for(i=(bytes.length - nbBytes); i < (nbBytes - 1); i++) {
                    buffer[i] = (byte)(buffer[i] << (8 - bits));
                    buffer[i] |= ((buffer[i+1] >>> bits) & nomasks[bits]);
                }
                buffer[i] = (byte)(buffer[i] << (8 - bits));
                buffer[i] &= masks[bits];
            }
        }
        markers.clear();
    }

    /**
     * Extracts some bytes at the beginning of the buffer. Read bytes are discarded from the buffer.
     * @param nbBytes   Number of bytes to be read
     * @return          Byte array containing the 'nbBytes' first bytes of the buffer.
     *                  Byte array's length may be shorter than requested if buffer does not 
     *                  contain enough bytes
     * @see             getBytes()                 
     */
    public byte[] readBytes(int nbBytes) {
        byte[] result = getBytes(0, nbBytes);
        int newLength = getNbBits() - (nbBytes * 8);

        if(result != null) {
            if(newLength > 0) {
                setBits(getBits(nbBytes * 8, newLength), newLength);
            }
            else {
                bits = 0;
                buffer = new byte[] {};
            }
        }
        return result;
        // TODO: move markers
    }

    /**
     * Extracts some bits at the beginning of the buffer. Read bits are discarded from the buffer.
     * @param nbBytes   Number of bits to be read
     * @return          Byte array containing the 'nbBits' first bits of the buffer.
     *                  Number of returned bits may be smaller than requested if buffer does not 
     *                  contain enough bits. Returned byte array is right-aligned. 
     *                  First byte may be a partial byte if 'nbBits' is not a multiple of 8. 
     *                  In this case MSBs of this byte are not significants and padded with '0's
     * @see             getBits()                 
     */
    public byte[] readBits(int nbBits) {
        byte[] result = getBits(0, nbBits);
        int newLength = getNbBits() - nbBits;
        byte[] newBuffer = getBits(nbBits, newLength);

        if(result != null) {
            setBits(newBuffer, newLength);
        }
        return result;
        // TODO: move markers
    }

    /**
     * Retrieves the raw content of the CodecBuffer
     * @return  Raw byte array used to store CodecBuffer's content
     *          Returned byte array is left-aligned. 
     *          Last byte may be a partial byte if 'bits' is not null. 
     *          In this case LSBs of this byte are not significants and their value is undetermined
     */
    public byte[] getBytes() {
        return buffer;
    }

    /**
     * Retrieves some bytes from the CodecBuffer. Returned bytes are not removed from the buffer
     * @param start     Start point (octet index)
     * @param nbBytes   Number of bytes to be returned
     * @return          Extracted bytes.
     *                  Returned byte array is left-aligned. 
     *                  Last byte may be a "partial byte" if it is the last byte of CodecBuffer and if 'bits' is not null. 
     *                  In this case LSBs of this byte are not significants and their value is undetermined
     * @see             ReadBytes()
     */
    public byte[] getBytes(int start, int nbBytes) {

        if(start > buffer.length) {
            System.err.println("bad start: " + start);
            return null;
        }
        if((start + nbBytes) > buffer.length) {
            System.err.println("bad length: " + (start + nbBytes) + " (" + buffer.length + " bytes remaining)");
            return null;
        }

        if(nbBytes < 0) {
            System.err.println("bad length: " + (nbBytes) + " (" + buffer.length + " bytes remaining)");
            return null;
        }

        return ByteHelper.extract(buffer, start, nbBytes);
    }

    /**
     * Retrieves all the bits from the CodecBuffer. Returned bits are not removed from the buffer
     * @return          Extracted bits stored in a byte array.
     *                  Returned byte array is right-aligned. 
     *                  First byte may be a "partial byte" if 'nbBits' is not a multiple of 8. 
     *                  In this case MSBs of this byte are not significants and their value is '0'
     * @see             ReadBits()
     */
    public byte[] getBits() {
        return getBits(0, getNbBits());
    }

    /**
     * Retrieves some bits from the CodecBuffer. Returned bits are not removed from the buffer
     * @param start     Start point (bit index)
     * @param nbBits    Number of bits to be returned
     * @return          Extracted bits stored in a byte array.
     *                  Returned byte array is right-aligned. 
     *                  First byte may be a "partial byte" if 'nbBits' is not a multiple of 8. 
     *                  In this case MSBs of this byte are not significants and their value is '0'
     * @see             ReadBits()
     */
    public byte[] getBits(int start, int nbBits) {

        int byteIndex = start / 8;
        int bitOffset = start % 8;
        int nbBytes = ((bitOffset > 0)?1:0) + (((nbBits - 8 + bitOffset) / 8) + ((bitOffset==0)?1:0)) + ((((nbBits - 8 + bitOffset) % 8) > 0)?1:0);
        //            leading partial byte    complete bytes                                            trailing partial byte  

        if(byteIndex > buffer.length) {
            System.err.println("bad start: " + byteIndex + "(" + start + ")"  + " (" + buffer.length + " bytes remaining)");
            return null;
        }
        if((byteIndex + nbBytes) > buffer.length) {
            System.err.println("bad length: " + (byteIndex + nbBytes) + "(" + nbBits + ")" + " (" + buffer.length + " bytes remaining)");
            return null;
        }

        byte[] tmp = ByteHelper.extract(buffer, byteIndex, nbBytes);

        if(bitOffset != 0) {
            tmp[0] = (byte)(tmp[0] << bitOffset);
            for(int i=1; i < tmp.length; i++) {
                tmp[i-1] &= masks[8 - bitOffset];
                tmp[i-1] |= ((tmp[i] >>> (8 - bitOffset)) & nomasks[8 - bitOffset]);
                tmp[i] = (byte)(tmp[i] << bitOffset);
            }
        }

        tmp = ByteHelper.extract(tmp, 0, nbBits / 8 + (((nbBits % 8) > 0)?1:0));

        if(nbBits % 8 > 0) {
            for(int i=tmp.length-1; i >= 0; i--) {
                tmp[i] = (byte)((tmp[i] >>> (8 - (nbBits % 8))) & nomasks[8 - (nbBits % 8)]);
                if(i > 0) {
                    tmp[i] |= (byte)(tmp[i-1] << (nbBits % 8));
                }
            }
        }

        return tmp;
    }

    /**
     * Retrieves some bits from the CodecBuffer. Returned bits are not removed from the buffer
     * @param markerKey Name of the marker serving as starting point for the extraction
     * @param nbBits    Number of bits to be returned
     * @return          Extracted bits stored in a byte array.
     *                  Returned byte array is right-aligned. 
     *                  First byte may be a "partial byte" if 'nbBits' is not a multiple of 8. 
     *                  In this case MSBs of this byte are not significants and their value is '0'
     * @see             ReadBits()
     */
    public byte[] getBits(String markerKey, int nbBits) {
        Marker marker = markers.get(markerKey);

        if(marker != null) {
            return getBits(marker.getPos(), nbBits);
        }
        return null;
    }
    
    /**
     * Private class used to represent markers that can be insterted in CodecBuffer
     */
    private class Marker {

        /**
         * Constructor.
         * @param pos       Position of the marker relative to the current position
         * @param callback  Optional callback object to be executed later, or null
         */
        public Marker(int pos, IMarkerCallback callback) {
            this.pos = pos;
            this.callback = callback;
        }

        /**
         * Retrieve the position of the marker (bit offset from buffer's start).
         * @return  The position of the marker
         */
        public int getPos() {
            return pos;
        }

        /**
         * Retrieve the callback object associated to the marker.
         * @return  The callback object associated to the marker, or null
         */
        public IMarkerCallback getCallback() {
            return callback;
        }

        /**
         * Changes the position of the marker in the buffer
         * @param nbBits    Position offset from marker's current position (can be negative)
         */
        public void move(int nbBits) {
            pos += nbBits;
        }

        /**
         * Current position of the marker
         */
        private int pos;
        
        
        /**
         * Callback object associated to the marker
         */
        private IMarkerCallback callback;
    }
}