/* * Copyright 2014-2016 CyberVision, Inc. * * 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 org.kaaproject.kaa.server.sync.platform; import org.kaaproject.kaa.common.Constants; import org.kaaproject.kaa.server.common.Base64Util; import org.kaaproject.kaa.server.sync.ClientSync; import org.kaaproject.kaa.server.sync.ClientSyncMetaData; import org.kaaproject.kaa.server.sync.ConfigurationClientSync; import org.kaaproject.kaa.server.sync.ConfigurationServerSync; import org.kaaproject.kaa.server.sync.EndpointAttachRequest; import org.kaaproject.kaa.server.sync.EndpointAttachResponse; import org.kaaproject.kaa.server.sync.EndpointDetachRequest; import org.kaaproject.kaa.server.sync.EndpointDetachResponse; import org.kaaproject.kaa.server.sync.Event; import org.kaaproject.kaa.server.sync.EventClientSync; import org.kaaproject.kaa.server.sync.EventListenersRequest; import org.kaaproject.kaa.server.sync.EventListenersResponse; import org.kaaproject.kaa.server.sync.EventServerSync; import org.kaaproject.kaa.server.sync.LogClientSync; import org.kaaproject.kaa.server.sync.LogDeliveryStatus; import org.kaaproject.kaa.server.sync.LogEntry; import org.kaaproject.kaa.server.sync.LogServerSync; import org.kaaproject.kaa.server.sync.Notification; import org.kaaproject.kaa.server.sync.NotificationClientSync; import org.kaaproject.kaa.server.sync.NotificationServerSync; import org.kaaproject.kaa.server.sync.NotificationType; import org.kaaproject.kaa.server.sync.ProfileClientSync; import org.kaaproject.kaa.server.sync.ProfileServerSync; import org.kaaproject.kaa.server.sync.RedirectServerSync; import org.kaaproject.kaa.server.sync.ServerSync; import org.kaaproject.kaa.server.sync.SubscriptionCommand; import org.kaaproject.kaa.server.sync.SubscriptionCommandType; import org.kaaproject.kaa.server.sync.SubscriptionType; import org.kaaproject.kaa.server.sync.SyncResponseStatus; import org.kaaproject.kaa.server.sync.SyncStatus; import org.kaaproject.kaa.server.sync.Topic; import org.kaaproject.kaa.server.sync.TopicState; import org.kaaproject.kaa.server.sync.UserAttachNotification; import org.kaaproject.kaa.server.sync.UserAttachRequest; import org.kaaproject.kaa.server.sync.UserAttachResponse; import org.kaaproject.kaa.server.sync.UserClientSync; import org.kaaproject.kaa.server.sync.UserDetachNotification; import org.kaaproject.kaa.server.sync.UserServerSync; import org.kaaproject.kaa.server.sync.bootstrap.BootstrapClientSync; import org.kaaproject.kaa.server.sync.bootstrap.BootstrapServerSync; import org.kaaproject.kaa.server.sync.bootstrap.ProtocolConnectionData; import org.kaaproject.kaa.server.sync.bootstrap.ProtocolVersionId; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * This class is an implementation of {@link PlatformEncDec} that uses internal * binary protocol for data serialization. * * @author Andrew Shvayka */ @KaaPlatformProtocol public class BinaryEncDec implements PlatformEncDec { public static final short PROTOCOL_VERSION = 1; public static final int MIN_SUPPORTED_VERSION = 1; public static final int MAX_SUPPORTED_VERSION = 1; static final int PADDING_SIZE = 4; // Options static final byte FAILURE = 0x01; static final byte RESYNC = 0x01; static final byte NOTHING = 0x00; // Notification types static final byte SYSTEM = 0x00; static final byte CUSTOM = 0x01; // Subscription types static final byte MANDATORY = 0x00; static final byte OPTIONAL = 0x01; // Extension constants static final byte BOOTSTRAP_EXTENSION_ID = 0; static final byte META_DATA_EXTENSION_ID = 1; static final byte PROFILE_EXTENSION_ID = 2; static final byte USER_EXTENSION_ID = 3; static final byte LOGGING_EXTENSION_ID = 4; static final byte CONFIGURATION_EXTENSION_ID = 5; static final byte NOTIFICATION_EXTENSION_ID = 6; static final byte EVENT_EXTENSION_ID = 7; private static final int EVENT_SEQ_NUMBER_REQUEST_OPTION = 0x02; private static final int CONFIGURATION_HASH_OPTION = 0x02; private static final int CONFIGURATION_RESYNC_OPTION = 0x04; // General constants private static final Logger LOG = LoggerFactory.getLogger(BinaryEncDec.class); private static final Charset UTF8 = Charset.forName("UTF-8"); private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.wrap(new byte[0]); private static final int DEFAULT_BUFFER_SIZE = 128; private static final int SIZE_OF_INT = 4; private static final int EXTENSIONS_COUNT_POSITION = 6; private static final int MIN_SIZE_OF_MESSAGE_HEADER = 8; private static final int MIN_SIZE_OF_EXTENSION_HEADER = 8; private static final byte SUCCESS = 0x00; private static final byte USER_SYNC_ENDPOINT_ID_OPTION = 0x01; private static final short EVENT_DATA_IS_EMPTY_OPTION = (short) 0x02; private static final int CLIENT_EVENT_DATA_IS_PRESENT_OPTION = 0x02; private static final int CLIENT_META_SYNC_SDK_TOKEN_OPTION = 0x08; private static final int CLIENT_META_SYNC_PROFILE_HASH_OPTION = 0x04; private static final int CLIENT_META_SYNC_KEY_HASH_OPTION = 0x02; private static final int CLIENT_META_SYNC_TIMEOUT_OPTION = 0x01; // Meta data constants private static final int PUBLIC_KEY_HASH_SIZE = 20; private static final int PROFILE_HASH_SIZE = 20; private static final int CONFIGURATION_HASH_SIZE = 20; private static final int TOPIC_LIST_HASH_SIZE = 20; // Profile client sync fields private static final byte CONF_SCHEMA_VERSION_FIELD_ID = 0; private static final byte PROFILE_SCHEMA_VERSION_FIELD_ID = 1; private static final byte SYSTEM_NOTIFICATION_SCHEMA_VERSION_FIELD_ID = 2; private static final byte USER_NOTIFICATION_SCHEMA_VERSION_FIELD_ID = 3; private static final byte LOG_SCHEMA_VERSION_FIELD_ID = 4; private static final byte EVENT_FAMILY_VERSIONS_COUNT_FIELD_ID = 5; private static final byte PUBLIC_KEY_FIELD_ID = 6; private static final byte ACCESS_TOKEN_FIELD_ID = 7; // User client sync fields private static final byte USER_ATTACH_FIELD_ID = 0; private static final byte ENDPOINT_ATTACH_FIELD_ID = 1; private static final byte ENDPOINT_DETACH_FIELD_ID = 2; // User server sync fields private static final byte USER_ATTACH_RESPONSE_FIELD_ID = 0; private static final byte USER_ATTACH_NOTIFICATION_FIELD_ID = 1; private static final byte USER_DETACH_NOTIFICATION_FIELD_ID = 2; private static final byte ENDPOINT_ATTACH_RESPONSE_FIELD_ID = 3; private static final byte ENDPOINT_DETACH_RESPONSE_FIELD_ID = 4; // Event client sync fields private static final byte EVENT_LISTENERS_FIELD_ID = 0; private static final byte EVENT_LIST_FIELD_ID = 1; // Event server sync fields private static final byte EVENT_LISTENERS_RESPONSE_FIELD_ID = 0; private static final byte EVENT_LIST_RESPONSE_FIELD_ID = 1; // Notification client sync fields private static final byte NF_TOPIC_STATES_FIELD_ID = 0; private static final byte NF_UNICAST_LIST_FIELD_ID = 1; private static final byte NF_SUBSCRIPTION_ADD_FIELD_ID = 2; private static final byte NF_SUBSCRIPTION_REMOVE_FIELD_ID = 3; // Notification server sync fields private static final byte NF_TOPICS_FIELD_ID = 0; private static final byte NF_NOTIFICATIONS_FIELD_ID = 1; private static int getIntFromUnsignedShort(ByteBuffer buf) { // handle unsigned integers from client return buf.getChar(); } private static boolean hasOption(int options, int option) { return (options & option) > 0; } private static String getUtf8String(ByteBuffer buf) { return getUtf8String(buf, getIntFromUnsignedShort(buf)); } private static String getUtf8String(ByteBuffer buf, int size) { return new String(getNewByteArray(buf, size), UTF8); } private static byte[] getNewByteArray(ByteBuffer buf, int size, boolean withPadding) { byte[] array = new byte[size]; buf.get(array); if (withPadding) { handlePadding(buf, size); } return array; } private static byte[] getNewByteArray(ByteBuffer buf, int size) { return getNewByteArray(buf, size, true); } private static ByteBuffer getNewByteBuffer(ByteBuffer buf, int size) { return getNewByteBuffer(buf, size, true); } private static ByteBuffer getNewByteBuffer(ByteBuffer buf, int size, boolean withPadding) { return ByteBuffer.wrap(getNewByteArray(buf, size, withPadding)); } private static void handlePadding(ByteBuffer buf, int size) { int padding = size % PADDING_SIZE; if (padding > 0) { buf.position(buf.position() + (PADDING_SIZE - padding)); } } /* * (non-Javadoc) * * @see * org.kaaproject.kaa.server.operations.service.akka.actors.io.platform. * PlatformEncDec#getId() */ @Override public int getId() { return Constants.KAA_PLATFORM_PROTOCOL_BINARY_ID; } @Override public ClientSync decode(byte[] data) throws PlatformEncDecException { if (LOG.isTraceEnabled()) { LOG.trace("Decoding binary data {}", Arrays.toString(data)); } ByteBuffer buf = ByteBuffer.wrap(data); if (buf.remaining() < MIN_SIZE_OF_MESSAGE_HEADER) { throw new PlatformEncDecException( MessageFormat.format("Message header is to small {0} to be kaa binary message!", buf.capacity())); } int protocolId = buf.getInt(); if (protocolId != getId()) { throw new PlatformEncDecException( MessageFormat.format("Unknown protocol id {0}!", protocolId)); } int protocolVersion = getIntFromUnsignedShort(buf); if (protocolVersion < MIN_SUPPORTED_VERSION || protocolVersion > MAX_SUPPORTED_VERSION) { throw new PlatformEncDecException( MessageFormat.format("Can't decode data using protocol version {0}!", protocolVersion)); } int extensionsCount = getIntFromUnsignedShort(buf); LOG.trace("received data for protocol id {} and version {} that contain {} extensions", protocolId, protocolVersion, extensionsCount); ClientSync sync = parseExtensions(buf, protocolVersion, extensionsCount); sync.setUseConfigurationRawSchema(false); LOG.trace("Decoded binary data {}", sync); return sync; } @Override public byte[] encode(ServerSync sync) throws PlatformEncDecException { LOG.trace("Encoding server sync {}", sync); GrowingByteBuffer buf = new GrowingByteBuffer(DEFAULT_BUFFER_SIZE); buf.putInt(getId()); buf.putShort(PROTOCOL_VERSION); buf.putShort(NOTHING); // will be updated later encodeMetaData(buf, sync); short extensionCount = 1; if (sync.getBootstrapSync() != null) { encode(buf, sync.getBootstrapSync()); extensionCount++; } if (sync.getProfileSync() != null) { encode(buf, sync.getProfileSync()); extensionCount++; } if (sync.getUserSync() != null) { encode(buf, sync.getUserSync()); extensionCount++; } if (sync.getLogSync() != null) { encode(buf, sync.getLogSync()); extensionCount++; } if (sync.getConfigurationSync() != null) { encode(buf, sync.getConfigurationSync()); extensionCount++; } if (sync.getNotificationSync() != null) { encode(buf, sync.getNotificationSync()); extensionCount++; } if (sync.getEventSync() != null) { encode(buf, sync.getEventSync()); extensionCount++; } if (sync.getRedirectSync() != null) { encode(buf, sync.getRedirectSync()); extensionCount++; } buf.putShort(EXTENSIONS_COUNT_POSITION, extensionCount); byte[] result = buf.toByteArray(); if (LOG.isTraceEnabled()) { LOG.trace("Encoded binary data {}", result); } return result; } private void encode(GrowingByteBuffer buf, BootstrapServerSync bootstrapSync) { buildExtensionHeader(buf, BOOTSTRAP_EXTENSION_ID, NOTHING, NOTHING, 0); final int extPosition = buf.position(); buf.putShort((short) bootstrapSync.getRequestId()); buf.putShort((short) bootstrapSync.getProtocolList().size()); for (ProtocolConnectionData data : bootstrapSync.getProtocolList()) { buf.putInt(data.getAccessPointId()); buf.putInt(data.getProtocolId()); buf.putShort((short) data.getProtocolVersion()); buf.putShort((short) data.getConnectionData().length); put(buf, data.getConnectionData()); } buf.putInt(extPosition - SIZE_OF_INT, buf.position() - extPosition); } private void encode(GrowingByteBuffer buf, ProfileServerSync profileSync) { buildExtensionHeader(buf, PROFILE_EXTENSION_ID, NOTHING, (profileSync.getResponseStatus() == SyncResponseStatus.RESYNC ? RESYNC : NOTHING), 0); } private void encode(GrowingByteBuffer buf, UserServerSync userSync) { buildExtensionHeader(buf, USER_EXTENSION_ID, NOTHING, NOTHING, 0); final int extPosition = buf.position(); if (userSync.getUserAttachResponse() != null) { UserAttachResponse uaResponse = userSync.getUserAttachResponse(); buf.put(USER_ATTACH_RESPONSE_FIELD_ID); buf.put(NOTHING); buf.put(uaResponse.getResult() == SyncStatus.SUCCESS ? SUCCESS : FAILURE); buf.put(NOTHING); if (uaResponse.getResult() != SyncStatus.SUCCESS) { buf.putShort((short) (uaResponse.getErrorCode() != null ? uaResponse.getErrorCode().ordinal() : 0)); if (uaResponse.getErrorReason() != null) { byte[] data = uaResponse.getErrorReason().getBytes(UTF8); buf.putShort((short) data.length); put(buf, data); } else { buf.putShort((short) 0); } } } if (userSync.getUserAttachNotification() != null) { UserAttachNotification nf = userSync.getUserAttachNotification(); buf.put(USER_ATTACH_NOTIFICATION_FIELD_ID); buf.put((byte) nf.getUserExternalId().length()); buf.putShort((short) nf.getEndpointAccessToken().length()); putUtf(buf, nf.getUserExternalId()); putUtf(buf, nf.getEndpointAccessToken()); } if (userSync.getUserDetachNotification() != null) { UserDetachNotification nf = userSync.getUserDetachNotification(); buf.put(USER_DETACH_NOTIFICATION_FIELD_ID); buf.put(NOTHING); buf.putShort((short) nf.getEndpointAccessToken().length()); putUtf(buf, nf.getEndpointAccessToken()); } if (userSync.getEndpointAttachResponses() != null) { buf.put(ENDPOINT_ATTACH_RESPONSE_FIELD_ID); buf.put(NOTHING); buf.putShort((short) userSync.getEndpointAttachResponses().size()); for (EndpointAttachResponse response : userSync.getEndpointAttachResponses()) { buf.put(response.getResult() == SyncStatus.SUCCESS ? SUCCESS : FAILURE); if (response.getEndpointKeyHash() != null) { buf.put(USER_SYNC_ENDPOINT_ID_OPTION); } else { buf.put(NOTHING); } buf.putShort((short) response.getRequestId()); if (response.getEndpointKeyHash() != null) { put(buf, Base64Util.decode(response.getEndpointKeyHash())); } } } if (userSync.getEndpointDetachResponses() != null) { buf.put(ENDPOINT_DETACH_RESPONSE_FIELD_ID); buf.put(NOTHING); buf.putShort((short) userSync.getEndpointDetachResponses().size()); for (EndpointDetachResponse response : userSync.getEndpointDetachResponses()) { buf.put(response.getResult() == SyncStatus.SUCCESS ? SUCCESS : FAILURE); buf.put(NOTHING); buf.putShort((short) response.getRequestId()); } } buf.putInt(extPosition - SIZE_OF_INT, buf.position() - extPosition); } private void encode(GrowingByteBuffer buf, LogServerSync logSync) { List<LogDeliveryStatus> statusList = logSync.getDeliveryStatuses(); int extensionSize = 4; if (statusList != null) { extensionSize += 4 * statusList.size(); } buildExtensionHeader(buf, LOGGING_EXTENSION_ID, NOTHING, NOTHING, extensionSize); if (statusList != null && !statusList.isEmpty()) { buf.putInt(statusList.size()); for (LogDeliveryStatus status : statusList) { buf.putShort((short) status.getRequestId()); buf.put(status.getResult() == SyncStatus.SUCCESS ? SUCCESS : FAILURE); buf.put(status.getErrorCode() != null ? (byte) status.getErrorCode().ordinal() : NOTHING); } } else { buf.putInt(0); } } private void encode(GrowingByteBuffer buf, ConfigurationServerSync configurationSync) { int option = 0; ByteBuffer confSchemaBody = configurationSync.getConfSchemaBody(); ByteBuffer confDeltaBody = configurationSync.getConfDeltaBody(); boolean confSchemaPresent = confSchemaBody != null && confSchemaBody.hasArray() && confSchemaBody.array().length != 0; boolean confBodyPresent = confDeltaBody != null && confDeltaBody.hasArray() && confDeltaBody.array().length != 0; if (confSchemaPresent) { option |= 0x01; } if (confBodyPresent) { option |= 0x02; } buildExtensionHeader(buf, CONFIGURATION_EXTENSION_ID, NOTHING, (byte) option, 0); final int extPosition = buf.position(); if (confSchemaPresent) { buf.putInt(confSchemaBody.array().length); } if (confBodyPresent) { buf.putInt(confDeltaBody.array().length); } if (confSchemaPresent) { put(buf, confSchemaBody.array()); } if (confBodyPresent) { put(buf, confDeltaBody.array()); } buf.putInt(extPosition - SIZE_OF_INT, buf.position() - extPosition); } private void encode(GrowingByteBuffer buf, NotificationServerSync notificationSync) { buildExtensionHeader(buf, NOTIFICATION_EXTENSION_ID, NOTHING, NOTHING, 0); final int extPosition = buf.position(); SyncResponseStatus status = notificationSync.getResponseStatus(); switch (status) { case NO_DELTA: buf.putInt(0); break; case DELTA: buf.putInt(1); break; case RESYNC: buf.putInt(2); break; default: break; } if (notificationSync.getAvailableTopics() != null) { buf.put(NF_TOPICS_FIELD_ID); buf.put(NOTHING); buf.putShort((short) notificationSync.getAvailableTopics().size()); for (Topic t : notificationSync.getAvailableTopics()) { buf.putLong(t.getIdAsLong()); buf.put(t.getSubscriptionType() == SubscriptionType.MANDATORY ? MANDATORY : OPTIONAL); buf.put(NOTHING); buf.putShort((short) t.getName().getBytes(UTF8).length); putUtf(buf, t.getName()); } } if (notificationSync.getNotifications() != null) { buf.put(NF_NOTIFICATIONS_FIELD_ID); buf.put(NOTHING); buf.putShort((short) notificationSync.getNotifications().size()); for (Notification nf : notificationSync.getNotifications()) { buf.putInt((nf.getSeqNumber() != null) ? nf.getSeqNumber() : 0); buf.put(nf.getType() == NotificationType.SYSTEM ? SYSTEM : CUSTOM); buf.put(NOTHING); buf.putShort(nf.getUid() != null ? (short) nf.getUid().length() : (short) 0); buf.putInt(nf.getBody().array().length); long topicId = nf.getTopicId() != null ? nf.getTopicIdAsLong() : 0L; buf.putLong(topicId); putUtf(buf, nf.getUid()); put(buf, nf.getBody().array()); } } buf.putInt(extPosition - SIZE_OF_INT, buf.position() - extPosition); } private void encode(GrowingByteBuffer buf, EventServerSync eventSync) { byte option = 0; if (eventSync.getEventSequenceNumberResponse() != null) { option = 1; } buildExtensionHeader(buf, EVENT_EXTENSION_ID, NOTHING, option, 0); final int extPosition = buf.position(); if (eventSync.getEventSequenceNumberResponse() != null) { buf.putInt(eventSync.getEventSequenceNumberResponse().getSeqNum()); } if (eventSync.getEventListenersResponses() != null && !eventSync.getEventListenersResponses().isEmpty()) { buf.put(EVENT_LISTENERS_RESPONSE_FIELD_ID); buf.put(NOTHING); buf.putShort((short) eventSync.getEventListenersResponses().size()); for (EventListenersResponse response : eventSync.getEventListenersResponses()) { buf.putShort((short) response.getRequestId()); buf.putShort(response.getResult() == SyncStatus.SUCCESS ? SUCCESS : FAILURE); if (response.getListeners() != null) { buf.putInt(response.getListeners().size()); for (String listener : response.getListeners()) { put(buf, Base64Util.decode(listener)); } } else { buf.putInt(0); } } } if (eventSync.getEvents() != null) { buf.put(EVENT_LIST_RESPONSE_FIELD_ID); buf.put(NOTHING); buf.putShort((short) eventSync.getEvents().size()); for (Event event : eventSync.getEvents()) { boolean eventDataIsEmpty = event.getEventData() == null || event.getEventData().array().length == 0; if (!eventDataIsEmpty) { buf.putShort(EVENT_DATA_IS_EMPTY_OPTION); } else { buf.putShort(NOTHING); } buf.putShort((short) event.getEventClassFqn().length()); if (!eventDataIsEmpty) { buf.putInt(event.getEventData().array().length); } buf.put(Base64Util.decode(event.getSource())); putUtf(buf, event.getEventClassFqn()); if (!eventDataIsEmpty) { put(buf, event.getEventData().array()); } } } buf.putInt(extPosition - SIZE_OF_INT, buf.position() - extPosition); } private void encode(GrowingByteBuffer buf, RedirectServerSync redirectSync) { buildExtensionHeader(buf, EVENT_EXTENSION_ID, NOTHING, NOTHING, 4); buf.putInt(redirectSync.getAccessPointId()); } /** * Put key and value, where key is <code>GrowingByteBuffer</code> instance and value is bytes * array. * * @param buf is <code>GrowingByteBuffer</code> instance * @param str is string which put in map as value */ public void putUtf(GrowingByteBuffer buf, String str) { if (str != null) { put(buf, str.getBytes(UTF8)); } } private void put(GrowingByteBuffer buf, byte[] data) { buf.put(data); int padding = data.length % BinaryEncDec.PADDING_SIZE; if (padding > 0) { padding = PADDING_SIZE - padding; for (int i = 0; i < padding; i++) { buf.put(NOTHING); } } } private ClientSync parseExtensions(ByteBuffer buf, int protocolVersion, int extensionsCount) throws PlatformEncDecException { ClientSync sync = new ClientSync(); for (short extPos = 0; extPos < extensionsCount; extPos++) { if (buf.remaining() < MIN_SIZE_OF_EXTENSION_HEADER) { throw new PlatformEncDecException(MessageFormat.format( "Extension header is to small. Available {0}, current possition is {1}!", buf.remaining(), buf.position())); } short type = buf.getShort(); int options = buf.getShort(); int payloadLength = buf.getInt(); if (buf.remaining() < payloadLength) { throw new PlatformEncDecException(MessageFormat.format( "Extension payload is to small. Available {0}, expected {1} current possition is {2}!", buf.remaining(), payloadLength, buf.position())); } switch (type) { case BOOTSTRAP_EXTENSION_ID: parseBootstrapClientSync(sync, buf, options, payloadLength); break; case META_DATA_EXTENSION_ID: parseClientSyncMetaData(sync, buf, options, payloadLength); break; case PROFILE_EXTENSION_ID: parseProfileClientSync(sync, buf, options, payloadLength); break; case USER_EXTENSION_ID: parseUserClientSync(sync, buf, options, payloadLength); break; case LOGGING_EXTENSION_ID: parseLogClientSync(sync, buf, options, payloadLength); break; case CONFIGURATION_EXTENSION_ID: parseConfigurationClientSync(sync, buf, options, payloadLength); break; case NOTIFICATION_EXTENSION_ID: parseNotificationClientSync(sync, buf, options, payloadLength); break; case EVENT_EXTENSION_ID: parseEventClientSync(sync, buf, options, payloadLength); break; default: break; } } return validate(sync); } private void buildExtensionHeader(GrowingByteBuffer buf, short extensionId, byte optionA, byte optionB, int length) { buf.putShort(extensionId); buf.put(optionA); buf.put(optionB); buf.putInt(length); } private void encodeMetaData(GrowingByteBuffer buf, ServerSync sync) { buildExtensionHeader(buf, META_DATA_EXTENSION_ID, NOTHING, NOTHING, 8); buf.putInt(sync.getRequestId()); buf.putInt(sync.getStatus().ordinal()); } private void parseClientSyncMetaData(ClientSync sync, ByteBuffer buf, int options, int payloadLength) throws PlatformEncDecException { sync.setRequestId(buf.getInt()); ClientSyncMetaData md = new ClientSyncMetaData(); if (hasOption(options, CLIENT_META_SYNC_TIMEOUT_OPTION)) { md.setTimeout((long) buf.getInt()); } if (hasOption(options, CLIENT_META_SYNC_KEY_HASH_OPTION)) { md.setEndpointPublicKeyHash(getNewByteBuffer(buf, PUBLIC_KEY_HASH_SIZE)); } if (hasOption(options, CLIENT_META_SYNC_PROFILE_HASH_OPTION)) { md.setProfileHash(getNewByteBuffer(buf, PROFILE_HASH_SIZE)); } if (hasOption(options, CLIENT_META_SYNC_SDK_TOKEN_OPTION)) { md.setSdkToken(getUtf8String(buf, Constants.SDK_TOKEN_SIZE)); } sync.setClientSyncMetaData(md); } private void parseBootstrapClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { int requestId = buf.getShort(); int protocolCount = buf.getShort(); List<ProtocolVersionId> keys = new ArrayList<>(protocolCount); for (int i = 0; i < protocolCount; i++) { keys.add(new ProtocolVersionId(buf.getInt(), buf.getShort())); // reserved buf.getShort(); } sync.setBootstrapSync(new BootstrapClientSync(requestId, keys)); } private void parseProfileClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { int payloadLimitPosition = buf.position() + payloadLength; ProfileClientSync profileSync = new ProfileClientSync(); profileSync.setProfileBody(getNewByteBuffer(buf, buf.getInt())); while (buf.position() < payloadLimitPosition) { byte fieldId = buf.get(); // reading unused reserved field buf.get(); switch (fieldId) { case PUBLIC_KEY_FIELD_ID: profileSync.setEndpointPublicKey(getNewByteBuffer(buf, getIntFromUnsignedShort(buf))); break; case ACCESS_TOKEN_FIELD_ID: profileSync.setEndpointAccessToken(getUtf8String(buf)); break; default: break; } } sync.setProfileSync(profileSync); } private void parseUserClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { int payloadLimitPosition = buf.position() + payloadLength; UserClientSync userSync = new UserClientSync(); while (buf.position() < payloadLimitPosition) { byte fieldId = buf.get(); switch (fieldId) { case USER_ATTACH_FIELD_ID: userSync.setUserAttachRequest(parseUserAttachRequest(buf)); break; case ENDPOINT_ATTACH_FIELD_ID: userSync.setEndpointAttachRequests(parseEndpointAttachRequests(buf)); break; case ENDPOINT_DETACH_FIELD_ID: userSync.setEndpointDetachRequests(parseEndpointDetachRequests(buf)); break; default: break; } } sync.setUserSync(userSync); } private void parseLogClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { LogClientSync logSync = new LogClientSync(); logSync.setRequestId(getIntFromUnsignedShort(buf)); int size = getIntFromUnsignedShort(buf); List<LogEntry> logs = new ArrayList<>(size); for (int i = 0; i < size; i++) { logs.add(new LogEntry(getNewByteBuffer(buf, buf.getInt()))); } logSync.setLogEntries(logs); sync.setLogSync(logSync); } private void parseConfigurationClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { ConfigurationClientSync confSync = new ConfigurationClientSync(); if (hasOption(options, CONFIGURATION_HASH_OPTION)) { confSync.setConfigurationHash(getNewByteBuffer(buf, CONFIGURATION_HASH_SIZE)); } if (hasOption(options, CONFIGURATION_RESYNC_OPTION)) { confSync.setResyncOnly(true); } sync.setConfigurationSync(confSync); } private void parseEventClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { EventClientSync eventSync = new EventClientSync(); if (hasOption(options, EVENT_SEQ_NUMBER_REQUEST_OPTION)) { eventSync.setSeqNumberRequest(true); } int payloadLimitPosition = buf.position() + payloadLength; while (buf.position() < payloadLimitPosition) { byte fieldId = buf.get(); // reading unused reserved field buf.get(); switch (fieldId) { case EVENT_LISTENERS_FIELD_ID: eventSync.setEventListenersRequests(parseListenerRequests(buf)); break; case EVENT_LIST_FIELD_ID: eventSync.setEvents(parseEvents(buf)); break; default: break; } } sync.setEventSync(eventSync); } private void parseNotificationClientSync(ClientSync sync, ByteBuffer buf, int options, int payloadLength) { int payloadLimitPosition = buf.position() + payloadLength; NotificationClientSync nfSync = new NotificationClientSync(); nfSync.setTopicListHash(buf.getInt()); while (buf.position() < payloadLimitPosition) { byte fieldId = buf.get(); // reading unused reserved field buf.get(); switch (fieldId) { case NF_TOPIC_STATES_FIELD_ID: nfSync.setTopicStates(parseTopicStates(buf)); break; case NF_UNICAST_LIST_FIELD_ID: nfSync.setAcceptedUnicastNotifications(parseUnicastIds(buf)); break; case NF_SUBSCRIPTION_ADD_FIELD_ID: parseSubscriptionCommands(nfSync, buf, true); break; case NF_SUBSCRIPTION_REMOVE_FIELD_ID: parseSubscriptionCommands(nfSync, buf, false); break; default: break; } } sync.setNotificationSync(nfSync); } private void parseSubscriptionCommands(NotificationClientSync nfSync, ByteBuffer buf, boolean add) { int count = getIntFromUnsignedShort(buf); if (nfSync.getSubscriptionCommands() == null) { nfSync.setSubscriptionCommands(new ArrayList<>()); } SubscriptionCommandType subscriptionType = add ? SubscriptionCommandType.ADD : SubscriptionCommandType.REMOVE; List<SubscriptionCommand> commands = new ArrayList<>(); for (int i = 0; i < count; i++) { long topicId = buf.getLong(); commands.add(new SubscriptionCommand(topicId, subscriptionType)); } nfSync.getSubscriptionCommands().addAll(commands); } private List<TopicState> parseTopicStates(ByteBuffer buf) { int count = getIntFromUnsignedShort(buf); List<TopicState> topicStates = new ArrayList<TopicState>(count); for (int i = 0; i < count; i++) { long topicId = buf.getLong(); int seqNumber = buf.getInt(); topicStates.add(new TopicState(topicId, seqNumber)); } return topicStates; } private List<String> parseUnicastIds(ByteBuffer buf) { int count = getIntFromUnsignedShort(buf); List<String> uids = new ArrayList<>(count); for (int i = 0; i < count; i++) { int uidLength = buf.getInt(); uids.add(getUtf8String(buf, uidLength)); } return uids; } private List<EventListenersRequest> parseListenerRequests(ByteBuffer buf) { int requestsCount = getIntFromUnsignedShort(buf); List<EventListenersRequest> requests = new ArrayList<>(requestsCount); for (int i = 0; i < requestsCount; i++) { int requestId = getIntFromUnsignedShort(buf); int fqnCount = getIntFromUnsignedShort(buf); List<String> fqns = new ArrayList<>(fqnCount); for (int j = 0; j < fqnCount; j++) { int fqnLength = getIntFromUnsignedShort(buf); // reserved buf.getShort(); fqns.add(getUtf8String(buf, fqnLength)); } requests.add(new EventListenersRequest(requestId, fqns)); } return requests; } private List<Event> parseEvents(ByteBuffer buf) { int eventsCount = getIntFromUnsignedShort(buf); List<Event> events = new ArrayList<>(eventsCount); for (int i = 0; i < eventsCount; i++) { Event event = new Event(); event.setSeqNum(buf.getInt()); int eventOptions = getIntFromUnsignedShort(buf); int fqnLength = getIntFromUnsignedShort(buf); int dataSize = 0; if (hasOption(eventOptions, CLIENT_EVENT_DATA_IS_PRESENT_OPTION)) { dataSize = buf.getInt(); } if (hasOption(eventOptions, 0x01)) { event.setTarget(Base64Util.encode(getNewByteArray(buf, PUBLIC_KEY_HASH_SIZE))); } event.setEventClassFqn(getUtf8String(buf, fqnLength)); if (dataSize > 0) { event.setEventData(getNewByteBuffer(buf, dataSize)); } else { event.setEventData(EMPTY_BUFFER); } events.add(event); } return events; } private List<EndpointAttachRequest> parseEndpointAttachRequests(ByteBuffer buf) { // reserved buf.get(); int count = getIntFromUnsignedShort(buf); List<EndpointAttachRequest> requests = new ArrayList<EndpointAttachRequest>(count); for (int i = 0; i < count; i++) { int requestId = getIntFromUnsignedShort(buf); String accessToken = getUtf8String(buf); requests.add(new EndpointAttachRequest(requestId, accessToken)); } return requests; } private List<EndpointDetachRequest> parseEndpointDetachRequests(ByteBuffer buf) { // reserved buf.get(); int count = getIntFromUnsignedShort(buf); List<EndpointDetachRequest> requests = new ArrayList<EndpointDetachRequest>(count); for (int i = 0; i < count; i++) { int requestId = getIntFromUnsignedShort(buf); // reserved buf.getShort(); requests.add(new EndpointDetachRequest( requestId, Base64Util.encode(getNewByteArray(buf, PUBLIC_KEY_HASH_SIZE)))); } return requests; } private UserAttachRequest parseUserAttachRequest(ByteBuffer buf) { int extIdLength = buf.get() & 0xFF; int tokenLength = getIntFromUnsignedShort(buf); int verifierTokenLength = getIntFromUnsignedShort(buf); // reserved buf.getShort(); String userExternalId = getUtf8String(buf, extIdLength); String userAccessToken = getUtf8String(buf, tokenLength); String userVerifierToken = getUtf8String(buf, verifierTokenLength); return new UserAttachRequest(userVerifierToken, userExternalId, userAccessToken); } private ClientSync validate(ClientSync sync) throws PlatformEncDecException { if (sync.getClientSyncMetaData() == null) { throw new PlatformEncDecException( MessageFormat.format("Input data does not have client sync meta data: {0}!", sync)); } return sync; } }