| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.rest; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.Socket; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.BlockingQueue; |
| import java.util.concurrent.LinkedBlockingQueue; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import org.apache.ignite.IgniteCheckedException; |
| import org.apache.ignite.IgniteLogger; |
| import org.apache.ignite.internal.util.typedef.internal.U; |
| import org.apache.ignite.logger.java.JavaLogger; |
| import org.apache.ignite.marshaller.Marshaller; |
| import org.apache.ignite.marshaller.jdk.JdkMarshaller; |
| import org.jetbrains.annotations.Nullable; |
| |
| /** |
| * Test client. |
| */ |
| final class TestMemcacheClient { |
| /** Header length. */ |
| private static final short HDR_LEN = 24; |
| |
| /** Serialized flag. */ |
| private static final short SERIALIZED_FLAG = 1; |
| |
| /** Boolean flag. */ |
| private static final short BOOLEAN_FLAG = (1 << 8); |
| |
| /** Integer flag. */ |
| private static final short INT_FLAG = (2 << 8); |
| |
| /** Long flag. */ |
| private static final short LONG_FLAG = (3 << 8); |
| |
| /** Date flag. */ |
| private static final short DATE_FLAG = (4 << 8); |
| |
| /** Byte flag. */ |
| private static final short BYTE_FLAG = (5 << 8); |
| |
| /** Float flag. */ |
| private static final short FLOAT_FLAG = (6 << 8); |
| |
| /** Double flag. */ |
| private static final short DOUBLE_FLAG = (7 << 8); |
| |
| /** Byte array flag. */ |
| private static final short BYTE_ARR_FLAG = (8 << 8); |
| |
| /** Logger. */ |
| private final IgniteLogger log = new JavaLogger(); |
| |
| /** JDK marshaller. */ |
| private final Marshaller jdkMarshaller = new JdkMarshaller(); |
| |
| /** Socket. */ |
| private final Socket sock; |
| |
| /** Opaque counter. */ |
| private final AtomicInteger opaqueCntr = new AtomicInteger(0); |
| |
| /** Response queue. */ |
| private final BlockingQueue<Response> queue = |
| new LinkedBlockingQueue<>(); |
| |
| /** Socket reader. */ |
| private final Thread rdr; |
| |
| /** Quit response. */ |
| private static final Response QUIT_RESP = new Response(0, false, null, null); |
| |
| /** |
| * Creates client. |
| * |
| * @param host Hostname. |
| * @param port Port number. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| TestMemcacheClient(String host, int port) throws IgniteCheckedException { |
| assert host != null; |
| assert port > 0; |
| |
| try { |
| sock = new Socket(host, port); |
| } |
| catch (IOException e) { |
| throw new IgniteCheckedException("Failed to establish connection.", e); |
| } |
| |
| // Start socket reader thread. |
| rdr = new Thread(new Runnable() { |
| @Override public void run() { |
| try { |
| InputStream in = sock.getInputStream(); |
| |
| ByteArrayOutputStream buf = new ByteArrayOutputStream(); |
| |
| boolean running = true; |
| |
| while (running) { |
| byte opCode = 0; |
| byte extrasLength = 0; |
| int keyLength = 0; |
| boolean success = false; |
| int totalLength = 0; |
| int opaque = 0; |
| short keyFlags = 0; |
| short valFlags = 0; |
| Object obj = null; |
| Object key = null; |
| |
| int i = 0; |
| |
| while (true) { |
| int symbol = in.read(); |
| |
| if (symbol == -1) { |
| running = false; |
| |
| break; |
| } |
| |
| byte b = (byte)symbol; |
| |
| if (i == 1) |
| opCode = b; |
| if (i == 2 || i == 3) { |
| buf.write(b); |
| |
| if (i == 3) { |
| keyLength = U.bytesToShort(buf.toByteArray(), 0); |
| |
| buf.reset(); |
| } |
| } |
| else if (i == 4) |
| extrasLength = b; |
| else if (i == 6 || i == 7) { |
| buf.write(b); |
| |
| if (i == 7) { |
| success = U.bytesToShort(buf.toByteArray(), 0) == 0; |
| |
| buf.reset(); |
| } |
| } |
| else if (i >= 8 && i <= 11) { |
| buf.write(b); |
| |
| if (i == 11) { |
| totalLength = U.bytesToInt(buf.toByteArray(), 0); |
| |
| buf.reset(); |
| } |
| } |
| else if (i >= 12 && i <= 15) { |
| buf.write(b); |
| |
| if (i == 15) { |
| opaque = U.bytesToInt(buf.toByteArray(), 0); |
| |
| buf.reset(); |
| } |
| } |
| else if (i >= HDR_LEN && i < HDR_LEN + extrasLength) { |
| buf.write(b); |
| |
| if (i == HDR_LEN + extrasLength - 1) { |
| byte[] rawFlags = buf.toByteArray(); |
| |
| keyFlags = U.bytesToShort(rawFlags, 0); |
| valFlags = U.bytesToShort(rawFlags, 2); |
| |
| buf.reset(); |
| } |
| } |
| else if (i >= HDR_LEN + extrasLength && i < HDR_LEN + extrasLength + keyLength) { |
| buf.write(b); |
| |
| if (i == HDR_LEN + extrasLength + keyLength - 1) { |
| key = decode(buf.toByteArray(), keyFlags); |
| |
| buf.reset(); |
| } |
| } |
| else if (i >= HDR_LEN + extrasLength + keyLength && i < HDR_LEN + totalLength) { |
| buf.write(b); |
| |
| if (opCode == 0x05 || opCode == 0x06) |
| valFlags = LONG_FLAG; |
| |
| if (i == HDR_LEN + totalLength - 1) { |
| obj = decode(buf.toByteArray(), valFlags); |
| |
| buf.reset(); |
| } |
| } |
| |
| if (i == HDR_LEN + totalLength - 1) { |
| queue.add(new Response(opaque, success, key, obj)); |
| |
| break; |
| } |
| |
| i++; |
| } |
| } |
| } |
| catch (IOException e) { |
| if (!Thread.currentThread().isInterrupted()) |
| U.error(log, e); |
| } |
| catch (Exception e) { |
| U.error(log, e); |
| } |
| finally { |
| U.closeQuiet(sock); |
| |
| queue.add(QUIT_RESP); |
| } |
| } |
| }); |
| |
| rdr.start(); |
| } |
| |
| /** */ |
| public void shutdown() throws IgniteCheckedException { |
| try { |
| if (rdr != null) { |
| rdr.interrupt(); |
| |
| U.closeQuiet(sock); |
| |
| rdr.join(); |
| } |
| } |
| catch (InterruptedException e) { |
| throw new IgniteCheckedException(e); |
| } |
| } |
| |
| /** |
| * Makes request to server and waits for response. |
| * |
| * @param cmd Command. |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @param extras Extras. |
| * @return Response. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| private Response makeRequest( |
| Command cmd, |
| @Nullable String cacheName, |
| @Nullable Object key, |
| @Nullable Object val, |
| @Nullable Long... extras |
| ) throws IgniteCheckedException { |
| assert cmd != null; |
| |
| int opaque = opaqueCntr.getAndIncrement(); |
| |
| // Send request. |
| try { |
| sock.getOutputStream().write(createPacket(cmd, cacheName, key, val, opaque, extras)); |
| } |
| catch (IOException e) { |
| throw new IgniteCheckedException("Failed to send packet.", e); |
| } |
| |
| // Wait for response. |
| while (true) { |
| try { |
| // Take response from queue. |
| Response res = queue.take(); |
| |
| if (res == QUIT_RESP) |
| return res; |
| |
| // Check opaque value. |
| if (res.getOpaque() == opaque) { |
| if (!res.isSuccess() && res.getObject() != null) |
| throw new IgniteCheckedException((String)res.getObject()); |
| else |
| return res; |
| } |
| else |
| // Return response to queue if opaque is incorrect. |
| queue.add(res); |
| } |
| catch (InterruptedException e) { |
| throw new IgniteCheckedException("Interrupted while waiting for response.", e); |
| } |
| } |
| } |
| |
| /** |
| * Makes request to server and waits for response. |
| * |
| * @param cmd Command. |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @param extras Extras. |
| * @return Response. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| private List<Response> makeMultiRequest( |
| Command cmd, |
| @Nullable String cacheName, |
| @Nullable Object key, |
| @Nullable Object val, |
| @Nullable Long... extras |
| ) throws IgniteCheckedException { |
| assert cmd != null; |
| |
| int opaque = opaqueCntr.getAndIncrement(); |
| |
| List<Response> resList = new LinkedList<>(); |
| |
| // Send request. |
| try { |
| sock.getOutputStream().write(createPacket(cmd, cacheName, key, val, opaque, extras)); |
| } |
| catch (IOException e) { |
| throw new IgniteCheckedException("Failed to send packet.", e); |
| } |
| |
| // Wait for response. |
| while (true) { |
| try { |
| // Take response from queue. |
| Response res = queue.take(); |
| |
| if (res == QUIT_RESP) |
| return resList; |
| |
| // Check opaque value. |
| if (res.getOpaque() == opaque) { |
| if (!res.isSuccess() && res.getObject() != null) |
| throw new IgniteCheckedException((String)res.getObject()); |
| else { |
| if (res.getObject() == null) |
| return resList; |
| |
| resList.add(res); |
| } |
| } |
| else |
| // Return response to queue if opaque is incorrect. |
| queue.add(res); |
| } |
| catch (InterruptedException e) { |
| throw new IgniteCheckedException("Interrupted while waiting for response.", e); |
| } |
| } |
| } |
| |
| /** |
| * Creates packet. |
| * |
| * @param cmd Command. |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @param opaque Opaque. |
| * @param extras Extras. |
| * @throws IgniteCheckedException In case of error. |
| * @return Packet. |
| */ |
| private byte[] createPacket( |
| Command cmd, |
| @Nullable String cacheName, |
| @Nullable Object key, |
| @Nullable Object val, |
| int opaque, |
| @Nullable Long[] extras |
| ) throws IgniteCheckedException { |
| assert cmd != null; |
| assert opaque >= 0; |
| |
| byte[] cacheNameBytes = cacheName != null ? cacheName.getBytes() : null; |
| |
| Data keyData = encode(key); |
| |
| Data valData = encode(val); |
| |
| int cacheNameLength = cacheNameBytes != null ? cacheNameBytes.length : 0; |
| int extrasLength = cmd.extrasLength() + cacheNameLength; |
| |
| byte[] packet = new byte[HDR_LEN + extrasLength + keyData.length() + valData.length()]; |
| |
| packet[0] = (byte)0x80; |
| packet[1] = cmd.operationCode(); |
| |
| U.shortToBytes((short) keyData.length(), packet, 2); |
| |
| packet[4] = (byte)(extrasLength); |
| |
| U.intToBytes(extrasLength + keyData.length() + valData.length(), packet, 8); |
| U.intToBytes(opaque, packet, 12); |
| |
| if (extrasLength > 0) { |
| if (extras != null) { |
| int offset = HDR_LEN; |
| |
| for (Long extra : extras) { |
| if (extra != null) |
| U.longToBytes(extra, packet, offset); |
| |
| offset += 8; |
| } |
| } |
| else { |
| U.shortToBytes(keyData.getFlags(), packet, HDR_LEN); |
| U.shortToBytes(valData.getFlags(), packet, HDR_LEN + 2); |
| } |
| } |
| |
| if (cacheNameBytes != null) |
| U.arrayCopy(cacheNameBytes, 0, packet, HDR_LEN + cmd.extrasLength(), cacheNameLength); |
| |
| if (keyData.getBytes() != null) |
| U.arrayCopy(keyData.getBytes(), 0, packet, HDR_LEN + extrasLength, keyData.length()); |
| |
| if (valData.getBytes() != null) |
| U.arrayCopy(valData.getBytes(), 0, packet, HDR_LEN + extrasLength + keyData.length(), valData.length()); |
| |
| return packet; |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @return If value was actually put. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K, V> boolean cachePut(@Nullable String cacheName, K key, V val) |
| throws IgniteCheckedException { |
| assert key != null; |
| assert val != null; |
| |
| return makeRequest(Command.PUT, cacheName, key, val).isSuccess(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @return Value. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K, V> V cacheGet(@Nullable String cacheName, K key) |
| throws IgniteCheckedException { |
| assert key != null; |
| |
| return makeRequest(Command.GET, cacheName, key, null).getObject(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @return Whether entry was actually removed. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K> boolean cacheRemove(@Nullable String cacheName, K key) throws IgniteCheckedException { |
| assert key != null; |
| |
| return makeRequest(Command.REMOVE, cacheName, key, null).isSuccess(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @return Whether entry was added. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K, V> boolean cacheAdd(@Nullable String cacheName, K key, V val) |
| throws IgniteCheckedException { |
| assert key != null; |
| assert val != null; |
| |
| return makeRequest(Command.ADD, cacheName, key, val).isSuccess(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value. |
| * @return Whether value was actually replaced. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K, V> boolean cacheReplace(@Nullable String cacheName, K key, V val) |
| throws IgniteCheckedException { |
| assert key != null; |
| assert val != null; |
| |
| return makeRequest(Command.REPLACE, cacheName, key, val).isSuccess(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @throws IgniteCheckedException In case of error. |
| * @return Metrics map. |
| */ |
| public <K> Map<String, Long> cacheMetrics(@Nullable String cacheName) throws IgniteCheckedException { |
| List<Response> raw = makeMultiRequest(Command.CACHE_METRICS, cacheName, null, null); |
| |
| Map<String, Long> res = new HashMap<>(raw.size()); |
| |
| for (Response resp : raw) |
| res.put((String)resp.key(), Long.parseLong(String.valueOf(resp.<String>getObject()))); |
| |
| return res; |
| } |
| |
| /** |
| * @param key Key. |
| * @param init Initial value (optional). |
| * @param incr Amount to add. |
| * @return New value. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K> long increment(K key, @Nullable Long init, long incr) |
| throws IgniteCheckedException { |
| assert key != null; |
| |
| return makeRequest(Command.INCREMENT, null, key, null, incr, init).<Long>getObject(); |
| } |
| |
| /** |
| * @param key Key. |
| * @param init Initial value (optional). |
| * @param decr Amount to subtract. |
| * @return New value. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K> long decrement(K key, @Nullable Long init, long decr) |
| throws IgniteCheckedException { |
| assert key != null; |
| |
| return makeRequest(Command.DECREMENT, null, key, null, decr, init).<Long>getObject(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value to append. |
| * @return Whether operation succeeded. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K> boolean cacheAppend(@Nullable String cacheName, K key, String val) |
| throws IgniteCheckedException { |
| assert key != null; |
| assert val != null; |
| |
| return makeRequest(Command.APPEND, cacheName, key, val).isSuccess(); |
| } |
| |
| /** |
| * @param cacheName Cache name. |
| * @param key Key. |
| * @param val Value to prepend. |
| * @return Whether operation succeeded. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public <K> boolean cachePrepend(@Nullable String cacheName, K key, String val) |
| throws IgniteCheckedException { |
| assert key != null; |
| assert val != null; |
| |
| return makeRequest(Command.PREPEND, cacheName, key, val).isSuccess(); |
| } |
| |
| /** |
| * @return Version. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public String version() throws IgniteCheckedException { |
| return makeRequest(Command.VERSION, null, null, null).getObject(); |
| } |
| |
| /** |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public void noop() throws IgniteCheckedException { |
| Response res = makeRequest(Command.NOOP, null, null, null); |
| |
| assert res != null; |
| assert res.isSuccess(); |
| assert res.getObject() == null; |
| } |
| |
| /** |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public void quit() throws IgniteCheckedException { |
| makeRequest(Command.QUIT, null, null, null); |
| |
| assert sock.isClosed(); |
| } |
| |
| /** |
| * Encodes object. |
| * |
| * @param obj Object. |
| * @return Encoded data. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public Data encode(@Nullable Object obj) throws IgniteCheckedException { |
| if (obj == null) |
| return new Data(null, (short)0); |
| |
| byte[] bytes; |
| short flags = 0; |
| |
| if (obj instanceof String) |
| bytes = ((String)obj).getBytes(); |
| else if (obj instanceof Boolean) { |
| bytes = new byte[] {(byte)((Boolean)obj ? '1' : '0')}; |
| |
| flags |= BOOLEAN_FLAG; |
| } |
| else if (obj instanceof Integer) { |
| bytes = U.intToBytes((Integer) obj); |
| |
| flags |= INT_FLAG; |
| } |
| else if (obj instanceof Long) { |
| bytes = U.longToBytes((Long) obj); |
| |
| flags |= LONG_FLAG; |
| } |
| else if (obj instanceof Date) { |
| bytes = U.longToBytes(((Date) obj).getTime()); |
| |
| flags |= DATE_FLAG; |
| } |
| else if (obj instanceof Byte) { |
| bytes = new byte[] {(Byte)obj}; |
| |
| flags |= BYTE_FLAG; |
| } |
| else if (obj instanceof Float) { |
| bytes = U.intToBytes(Float.floatToIntBits((Float) obj)); |
| |
| flags |= FLOAT_FLAG; |
| } |
| else if (obj instanceof Double) { |
| bytes = U.longToBytes(Double.doubleToLongBits((Double) obj)); |
| |
| flags |= DOUBLE_FLAG; |
| } |
| else if (obj instanceof byte[]) { |
| bytes = (byte[])obj; |
| |
| flags |= BYTE_ARR_FLAG; |
| } |
| else { |
| bytes = jdkMarshaller.marshal(obj); |
| |
| flags |= SERIALIZED_FLAG; |
| } |
| |
| return new Data(bytes, flags); |
| } |
| |
| /** |
| * @param bytes Byte array to decode. |
| * @param flags Flags. |
| * @return Decoded value. |
| * @throws IgniteCheckedException In case of error. |
| */ |
| public Object decode(byte[] bytes, short flags) throws IgniteCheckedException { |
| assert bytes != null; |
| assert flags >= 0; |
| |
| if ((flags & SERIALIZED_FLAG) != 0) |
| return jdkMarshaller.unmarshal(bytes, getClass().getClassLoader()); |
| |
| int masked = flags & 0xff00; |
| |
| switch (masked) { |
| case BOOLEAN_FLAG: |
| return bytes[0] == '1'; |
| case INT_FLAG: |
| return U.bytesToInt(bytes, 0); |
| case LONG_FLAG: |
| return U.bytesToLong(bytes, 0); |
| case DATE_FLAG: |
| return new Date(U.bytesToLong(bytes, 0)); |
| case BYTE_FLAG: |
| return bytes[0]; |
| case FLOAT_FLAG: |
| return Float.intBitsToFloat(U.bytesToInt(bytes, 0)); |
| case DOUBLE_FLAG: |
| return Double.longBitsToDouble(U.bytesToLong(bytes, 0)); |
| case BYTE_ARR_FLAG: |
| return bytes; |
| default: |
| return new String(bytes); |
| } |
| } |
| |
| /** |
| * Response data. |
| */ |
| private static class Response { |
| /** Opaque. */ |
| private final int opaque; |
| |
| /** Success flag. */ |
| private final boolean success; |
| |
| /** Key. */ |
| private final Object key; |
| |
| /** Response object. */ |
| private final Object obj; |
| |
| /** |
| * @param opaque Opaque. |
| * @param success Success flag. |
| * @param key Key object. |
| * @param obj Response object. |
| */ |
| Response(int opaque, boolean success, @Nullable Object key, @Nullable Object obj) { |
| assert opaque >= 0; |
| |
| this.opaque = opaque; |
| this.success = success; |
| this.key = key; |
| this.obj = obj; |
| } |
| |
| /** |
| * @return Opaque. |
| */ |
| int getOpaque() { |
| return opaque; |
| } |
| |
| /** |
| * @return Success flag. |
| */ |
| boolean isSuccess() { |
| return success; |
| } |
| |
| /** */ |
| Object key() { |
| return key; |
| } |
| |
| /** |
| * @return Response object. |
| */ |
| @SuppressWarnings("unchecked") |
| <T> T getObject() { |
| return (T)obj; |
| } |
| } |
| |
| /** */ |
| private static class Data { |
| /** Bytes. */ |
| private final byte[] bytes; |
| |
| /** Flags. */ |
| private final short flags; |
| |
| /** |
| * @param bytes Bytes. |
| * @param flags Flags. |
| */ |
| Data(@Nullable byte[] bytes, short flags) { |
| assert flags >= 0; |
| |
| this.bytes = bytes; |
| this.flags = flags; |
| } |
| |
| /** |
| * @return Bytes. |
| */ |
| @Nullable public byte[] getBytes() { |
| return bytes; |
| } |
| |
| /** |
| * @return Flags. |
| */ |
| public short getFlags() { |
| return flags; |
| } |
| |
| /** |
| * @return Length. |
| */ |
| public int length() { |
| return bytes != null ? bytes.length : 0; |
| } |
| } |
| |
| /** |
| * Command. |
| */ |
| private enum Command { |
| /** Get. */ |
| GET((byte)0x00, 4), |
| |
| /** Put. */ |
| PUT((byte)0x01, 8), |
| |
| /** Add. */ |
| ADD((byte)0x02, 8), |
| |
| /** Replace. */ |
| REPLACE((byte)0x03, 8), |
| |
| /** Remove. */ |
| REMOVE((byte)0x04, 4), |
| |
| /** Increment. */ |
| INCREMENT((byte)0x05, 20), |
| |
| /** Decrement. */ |
| DECREMENT((byte)0x06, 20), |
| |
| /** Quit. */ |
| QUIT((byte)0x07, 0), |
| |
| /** Cache metrics. */ |
| CACHE_METRICS((byte)0x10, 4), |
| |
| /** No-op. */ |
| NOOP((byte)0x0A, 0), |
| |
| /** Version. */ |
| VERSION((byte)0x0B, 0), |
| |
| /** Append. */ |
| APPEND((byte)0x0E, 4), |
| |
| /** Append. */ |
| PREPEND((byte)0x0F, 4); |
| |
| /** Operation code. */ |
| private final byte opCode; |
| |
| /** Extras length. */ |
| private final int extrasLength; |
| |
| /** |
| * @param opCode Operation code. |
| * @param extrasLength Extras length. |
| */ |
| Command(byte opCode, int extrasLength) { |
| this.opCode = opCode; |
| this.extrasLength = extrasLength; |
| } |
| |
| /** |
| * @return Operation code. |
| */ |
| public byte operationCode() { |
| return opCode; |
| } |
| |
| /** |
| * @return Extras length. |
| */ |
| public int extrasLength() { |
| return extrasLength; |
| } |
| } |
| } |