private void ProcessBinaryMeasurements(IEnumerable<IBinaryMeasurement> measurements, long frameLevelTimestamp, bool useCompactMeasurementFormat, bool usePayloadCompression) { // Create working buffer using (BlockAllocatedMemoryStream workingBuffer = new BlockAllocatedMemoryStream()) { // Serialize data packet flags into response DataPacketFlags flags = DataPacketFlags.Synchronized; if (m_compressionModes.HasFlag(CompressionModes.TSSC)) { flags |= DataPacketFlags.Compressed; } else { if (useCompactMeasurementFormat) flags |= DataPacketFlags.Compact; } workingBuffer.WriteByte((byte)flags); // Serialize frame timestamp into data packet - this only occurs in synchronized data packets, // unsynchronized subscriptions always include timestamps in the serialized measurements workingBuffer.Write(BigEndian.GetBytes(frameLevelTimestamp), 0, 8); // Serialize total number of measurement values to follow workingBuffer.Write(BigEndian.GetBytes(measurements.Count()), 0, 4); if (usePayloadCompression && m_compressionModes.HasFlag(CompressionModes.TSSC)) { if ((object)m_compressionBlock == null) m_compressionBlock = new MeasurementCompressionBlock(); else m_compressionBlock.Clear(); foreach (CompactMeasurement measurement in measurements.Cast<CompactMeasurement>()) { if (m_compressionBlock.CanAddMeasurements) { m_compressionBlock.AddMeasurement(measurement.RuntimeID, measurement.Timestamp.Value, (uint)measurement.StateFlags, (float)measurement.AdjustedValue); } else { m_compressionBlock.CopyTo(workingBuffer); m_compressionBlock.Clear(); m_compressionBlock.AddMeasurement(measurement.RuntimeID, measurement.Timestamp.Value, (uint)measurement.StateFlags, (float)measurement.AdjustedValue); } } m_compressionBlock.CopyTo(workingBuffer); } else { // Attempt compression when requested - encoding of compressed buffer only happens if size would be smaller than normal serialization if (!usePayloadCompression || !measurements.Cast<CompactMeasurement>().CompressPayload(workingBuffer, m_compressionStrength, false, ref flags)) { // Serialize measurements to data buffer foreach (IBinaryMeasurement measurement in measurements) { measurement.CopyBinaryImageToStream(workingBuffer); } } // Update data packet flags if it has updated compression flags if ((flags & DataPacketFlags.Compressed) > 0) { workingBuffer.Seek(0, SeekOrigin.Begin); workingBuffer.WriteByte((byte)flags); } } // Publish data packet to client if ((object)m_parent != null) m_parent.SendClientResponse(m_clientID, ServerResponse.DataPacket, ServerCommand.Subscribe, workingBuffer.ToArray()); } }
/// <summary> /// Sends a server command to the publisher connection. /// </summary> /// <param name="commandCode"><see cref="ServerCommand"/> to send.</param> /// <param name="data">Optional command data to send.</param> /// <returns><c>true</c> if <paramref name="commandCode"/> transmission was successful; otherwise <c>false</c>.</returns> public virtual bool SendServerCommand(ServerCommand commandCode, byte[] data = null) { if ((object)m_commandChannel != null && m_commandChannel.CurrentState == ClientState.Connected) { try { using (BlockAllocatedMemoryStream commandPacket = new BlockAllocatedMemoryStream()) { // Write command code into command packet commandPacket.WriteByte((byte)commandCode); // Write command buffer into command packet if ((object)data != null && data.Length > 0) commandPacket.Write(data, 0, data.Length); // Send command packet to publisher m_commandChannel.SendAsync(commandPacket.ToArray(), 0, (int)commandPacket.Length); m_metadataRefreshPending = (commandCode == ServerCommand.MetaDataRefresh); } // Track server command in pending request queue lock (m_requests) { // Make sure a pending request does not already exist int index = m_requests.BinarySearch(commandCode); if (index < 0) { // Add the new server command to the request list m_requests.Add(commandCode); // Make sure requests are sorted to allow for binary searching m_requests.Sort(); } } return true; } catch (Exception ex) { OnProcessException(new InvalidOperationException($"Exception occurred while trying to send server command \"{commandCode}\" to publisher: {ex.Message}", ex)); } } else OnProcessException(new InvalidOperationException($"Subscriber is currently unconnected. Cannot send server command \"{commandCode}\" to publisher.")); return false; }
/// <summary> /// Subscribes (or re-subscribes) to a data publisher for a set of data points. /// </summary> /// <param name="remotelySynchronized">Boolean value that determines if subscription should be remotely synchronized - note that data publisher may not allow remote synchronization.</param> /// <param name="compactFormat">Boolean value that determines if the compact measurement format should be used. Set to <c>false</c> for full fidelity measurement serialization; otherwise set to <c>true</c> for bandwidth conservation.</param> /// <param name="connectionString">Connection string that defines required and optional parameters for the subscription.</param> /// <returns><c>true</c> if subscribe transmission was successful; otherwise <c>false</c>.</returns> public virtual bool Subscribe(bool remotelySynchronized, bool compactFormat, string connectionString) { bool success = false; if (!string.IsNullOrWhiteSpace(connectionString)) { try { // Parse connection string to see if it contains a data channel definition Dictionary<string, string> settings = connectionString.ParseKeyValuePairs(); UdpClient dataChannel = null; string setting; // Track specified time inclusion for later deserialization if (settings.TryGetValue("includeTime", out setting)) m_includeTime = setting.ParseBoolean(); else m_includeTime = true; settings.TryGetValue("dataChannel", out setting); if (!string.IsNullOrWhiteSpace(setting)) { dataChannel = new UdpClient(setting); dataChannel.ReceiveBufferSize = ushort.MaxValue; dataChannel.MaxConnectionAttempts = -1; dataChannel.ConnectAsync(); } // Assign data channel client reference and attach to needed events DataChannel = dataChannel; // Setup subscription packet using (BlockAllocatedMemoryStream buffer = new BlockAllocatedMemoryStream()) { DataPacketFlags flags = DataPacketFlags.NoFlags; byte[] bytes; if (remotelySynchronized) flags |= DataPacketFlags.Synchronized; if (compactFormat) flags |= DataPacketFlags.Compact; // Write data packet flags into buffer buffer.WriteByte((byte)flags); // Get encoded bytes of connection string bytes = m_encoding.GetBytes(connectionString); // Write encoded connection string length into buffer buffer.Write(BigEndian.GetBytes(bytes.Length), 0, 4); // Encode connection string into buffer buffer.Write(bytes, 0, bytes.Length); // Cache subscribed synchronization state m_synchronizedSubscription = remotelySynchronized; // Send subscribe server command with associated command buffer success = SendServerCommand(ServerCommand.Subscribe, buffer.ToArray()); } } catch (Exception ex) { OnProcessException(new InvalidOperationException("Exception occurred while trying to make publisher subscription: " + ex.Message, ex)); } } else OnProcessException(new InvalidOperationException("Cannot make publisher subscription without a connection string.")); // Reset decompressor on successful resubscription if (success && (object)m_decompressionBlock != null) m_decompressionBlock.Reset(); return success; }
/// <summary> /// Sends a server command to the publisher connection. /// </summary> /// <param name="commandCode"><see cref="ServerCommand"/> to send.</param> /// <param name="data">Optional command data to send.</param> /// <returns><c>true</c> if <paramref name="commandCode"/> transmission was successful; otherwise <c>false</c>.</returns> public virtual bool SendServerCommand(ServerCommand commandCode, byte[] data = null) { if ((object)m_commandChannel != null && m_commandChannel.CurrentState == ClientState.Connected) { try { using (BlockAllocatedMemoryStream commandPacket = new BlockAllocatedMemoryStream()) { // Write command code into command packet commandPacket.WriteByte((byte)commandCode); // Write command buffer into command packet if ((object)data != null && data.Length > 0) commandPacket.Write(data, 0, data.Length); // Send command packet to publisher m_commandChannel.SendAsync(commandPacket.ToArray(), 0, (int)commandPacket.Length); m_metadataRefreshPending = commandCode == ServerCommand.MetaDataRefresh; } return true; } catch (Exception ex) { OnProcessException(MessageLevel.Error, new InvalidOperationException($"Exception occurred while trying to send server command \"{commandCode}\" to publisher: {ex.Message}", ex)); } } else OnProcessException(MessageLevel.Error, new InvalidOperationException($"Subscriber is currently unconnected. Cannot send server command \"{commandCode}\" to publisher.")); return false; }
/// <summary> /// Rotates or initializes the crypto keys for this <see cref="ClientConnection"/>. /// </summary> public bool RotateCipherKeys() { // Make sure at least a second has passed before next key rotation if ((DateTime.UtcNow.Ticks - m_lastCipherKeyUpdateTime).ToMilliseconds() >= 1000.0D) { try { // Since this function cannot be not called more than once per second there // is no real benefit to maintaining these memory streams at a member level using (BlockAllocatedMemoryStream response = new BlockAllocatedMemoryStream()) { byte[] bytes, bufferLen; // Create or update cipher keys and initialization vectors UpdateKeyIVs(); // Add current cipher index to response response.WriteByte((byte)m_cipherIndex); // Serialize new keys using (BlockAllocatedMemoryStream buffer = new BlockAllocatedMemoryStream()) { // Write even key bufferLen = BigEndian.GetBytes(m_keyIVs[EvenKey][KeyIndex].Length); buffer.Write(bufferLen, 0, bufferLen.Length); buffer.Write(m_keyIVs[EvenKey][KeyIndex], 0, m_keyIVs[EvenKey][KeyIndex].Length); // Write even initialization vector bufferLen = BigEndian.GetBytes(m_keyIVs[EvenKey][IVIndex].Length); buffer.Write(bufferLen, 0, bufferLen.Length); buffer.Write(m_keyIVs[EvenKey][IVIndex], 0, m_keyIVs[EvenKey][IVIndex].Length); // Write odd key bufferLen = BigEndian.GetBytes(m_keyIVs[OddKey][KeyIndex].Length); buffer.Write(bufferLen, 0, bufferLen.Length); buffer.Write(m_keyIVs[OddKey][KeyIndex], 0, m_keyIVs[OddKey][KeyIndex].Length); // Write odd initialization vector bufferLen = BigEndian.GetBytes(m_keyIVs[OddKey][IVIndex].Length); buffer.Write(bufferLen, 0, bufferLen.Length); buffer.Write(m_keyIVs[OddKey][IVIndex], 0, m_keyIVs[OddKey][IVIndex].Length); // Get bytes from serialized buffer bytes = buffer.ToArray(); } // Encrypt keys using private keys known only to current client and server if (m_authenticated && !string.IsNullOrWhiteSpace(m_sharedSecret)) bytes = bytes.Encrypt(m_sharedSecret, CipherStrength.Aes256); // Add serialized key response response.Write(bytes, 0, bytes.Length); // Send cipher key updates m_parent.SendClientResponse(m_clientID, ServerResponse.UpdateCipherKeys, ServerCommand.Subscribe, response.ToArray()); } // Send success message m_parent.SendClientResponse(m_clientID, ServerResponse.Succeeded, ServerCommand.RotateCipherKeys, "New cipher keys established."); m_parent.OnStatusMessage(ConnectionID + " cipher keys rotated."); return true; } catch (Exception ex) { // Send failure message m_parent.SendClientResponse(m_clientID, ServerResponse.Failed, ServerCommand.RotateCipherKeys, "Failed to establish new cipher keys: " + ex.Message); m_parent.OnStatusMessage("Failed to establish new cipher keys for {0}: {1}", ConnectionID, ex.Message); return false; } } m_parent.SendClientResponse(m_clientID, ServerResponse.Failed, ServerCommand.RotateCipherKeys, "Cipher key rotation skipped, keys were already rotated within last second."); m_parent.OnStatusMessage("WARNING: Cipher key rotation skipped for {0}, keys were already rotated within last second.", ConnectionID); return false; }
private void ProcessBinaryMeasurements(IEnumerable<IBinaryMeasurement> measurements, bool useCompactMeasurementFormat, bool usePayloadCompression) { // Create working buffer using (BlockAllocatedMemoryStream workingBuffer = new BlockAllocatedMemoryStream()) { // Serialize data packet flags into response DataPacketFlags flags = DataPacketFlags.NoFlags; // No flags means bit is cleared, i.e., unsynchronized if (useCompactMeasurementFormat) flags |= DataPacketFlags.Compact; workingBuffer.WriteByte((byte)flags); // No frame level timestamp is serialized into the data packet since all data is unsynchronized and essentially // published upon receipt, however timestamps are optionally included in the serialized measurements. // Serialize total number of measurement values to follow workingBuffer.Write(BigEndian.GetBytes(measurements.Count()), 0, 4); // Attempt compression when requested - encoding of compressed buffer only happens if size would be smaller than normal serialization if (!usePayloadCompression || !measurements.Cast<CompactMeasurement>().CompressPayload(workingBuffer, m_compressionStrength, m_includeTime, ref flags)) { // Serialize measurements to data buffer foreach (IBinaryMeasurement measurement in measurements) measurement.CopyBinaryImageToStream(workingBuffer); } // Update data packet flags if it has updated compression flags if ((flags & DataPacketFlags.Compressed) > 0) { workingBuffer.Seek(0, SeekOrigin.Begin); workingBuffer.WriteByte((byte)flags); } // Publish data packet to client if ((object)m_parent != null) m_parent.SendClientResponse(m_clientID, ServerResponse.DataPacket, ServerCommand.Subscribe, workingBuffer.ToArray()); // Track last publication time m_lastPublishTime = DateTime.UtcNow.Ticks; } }