/// <summary> /// See interface docs. /// </summary> /// <param name="message"></param> /// <returns></returns> public byte[] Compress(BaseStationMessage message) { byte[] result = null; if (IsMeaningfulMessage(message)) { using (var stream = new MemoryStream()) { using (var writer = new BinaryWriter(stream)) { stream.WriteByte(0); // <-- reserve a byte for the buffer length writer.Write((ushort)0); // <-- reserve a word for the checksum stream.WriteByte((byte)(int)message.TransmissionType); EncodeIcao(stream, message.Icao24); long optionalFlagsOffset = stream.Position; stream.WriteByte(0); // <-- reserve two bytes for the optional fields flags stream.WriteByte(0); // <-- " OptionalFields optionalFields = OptionalFields.None; if (!String.IsNullOrEmpty(message.Callsign)) { optionalFields |= OptionalFields.CallSign; EncodeString(stream, message.Callsign); } if (message.Altitude != null) { optionalFields |= OptionalFields.Altitude; EncodeFloatInt(stream, message.Altitude.Value); } if (message.GroundSpeed != null) { optionalFields |= OptionalFields.GroundSpeed; EncodeFloatShort(writer, message.GroundSpeed.Value); } if (message.Track != null) { optionalFields |= OptionalFields.Track; EncodeFloatShort(writer, message.Track.Value * 10f); } if (message.Latitude != null) { optionalFields |= OptionalFields.Latitude; EncodeFloat(writer, (float)message.Latitude.Value); } if (message.Longitude != null) { optionalFields |= OptionalFields.Longitude; EncodeFloat(writer, (float)message.Longitude.Value); } if (message.VerticalRate != null) { optionalFields |= OptionalFields.VerticalRate; EncodeFloatShort(writer, message.VerticalRate.Value); } if (message.Squawk != null) { optionalFields |= OptionalFields.Squawk; EncodeShort(writer, (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, message.Squawk.Value))); } CompressedFlags flags = CompressedFlags.None; bool hasFlag = false; if (message.SquawkHasChanged != null) { optionalFields |= OptionalFields.SquawkHasChanged; hasFlag = true; if (message.SquawkHasChanged.Value) { flags |= CompressedFlags.SquawkHasChanged; } } if (message.Emergency != null) { optionalFields |= OptionalFields.Emergency; hasFlag = true; if (message.Emergency.Value) { flags |= CompressedFlags.Emergency; } } if (message.IdentActive != null) { optionalFields |= OptionalFields.IdentActive; hasFlag = true; if (message.IdentActive.Value) { flags |= CompressedFlags.IdentActive; } } if (message.OnGround != null) { optionalFields |= OptionalFields.OnGround; hasFlag = true; if (message.OnGround.Value) { flags |= CompressedFlags.OnGround; } } if (hasFlag) { stream.WriteByte((byte)flags); } stream.Seek(optionalFlagsOffset, SeekOrigin.Begin); EncodeShort(writer, (short)optionalFields); } result = stream.ToArray(); if (result.Length != 0) { result[0] = (byte)result.Length; var checksum = CalculateChecksum(result); result[1] = (byte)(checksum & 0x00ff); result[2] = (byte)((checksum & 0xff00) >> 8); } } } return(result ?? new byte[0]); }
protected override void DeSerialise(byte[] buf, ref int o, int length) { RegionHandle = new RegionHandle(BinarySerializer.DeSerializeUInt64_Le(buf, ref o, length)); TimeDilation = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length); string logMessage = $"ObjectUpdateCompressed: RegionHandle={RegionHandle}, TimeDilation={TimeDilation}"; int nObjects = buf[o++]; for (int i = 0; i < nObjects; i++) { UInt32 len; ObjectUpdateMessage.ObjectData data = new ObjectUpdateMessage.ObjectData(); Objects.Add(data); data.UpdateFlags = (ObjectUpdateFlags)BinarySerializer.DeSerializeUInt32_Le(buf, ref o, length); int compressedLength = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length); byte[] compressedData = new byte[compressedLength]; Array.Copy(buf, o, compressedData, 0, compressedLength); o += compressedLength; int compressedOffset = 0; logMessage += $"\n Object {i}: UpdateFlags={data.UpdateFlags}, Data({compressedData.Length})={BitConverter.ToString(compressedData)}"; data.FullId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); data.LocalId = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.PCode = (PCode)compressedData[compressedOffset++]; data.State = compressedData[compressedOffset++]; data.Crc = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.Material = (MaterialType)compressedData[compressedOffset++]; data.ClickAction = (ClickAction)compressedData[compressedOffset++]; data.Scale = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); data.Position = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); data.Rotation = BinarySerializer.DeSerializeQuaternion(compressedData, ref compressedOffset, compressedLength); CompressedFlags compressedFlags = (CompressedFlags)BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.OwnerId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); logMessage += $"\n FullId={data.FullId}, LocalId={data.LocalId}, PCode={data.PCode}, State={data.State}, Crc={data.Crc}, Material={data.Material}, ClickAction={data.ClickAction}, Scale={data.Scale}, Position={data.Position}, Rotation={data.Rotation}, CompressedFlags=({compressedFlags})"; if ((compressedFlags & CompressedFlags.HasAngularVelocity) != 0) { data.AngularVelocity = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); logMessage += $", AngularVelocity={data.AngularVelocity}"; } data.ParentId = (compressedFlags & CompressedFlags.HasParent) != 0 ? BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength) : (uint)0; logMessage += $", ParentId={data.ParentId}"; if ((compressedFlags & CompressedFlags.Tree) != 0) { byte treeSpecies = compressedData[compressedOffset++]; logMessage += $", TreeSpecies={treeSpecies}"; } if ((compressedFlags & CompressedFlags.ScratchPad) != 0) { len = compressedData[compressedOffset++]; compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32 logMessage += $", Scratchpad({len})"; } if ((compressedFlags & CompressedFlags.HasText) != 0) { data.Text = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); data.TextColour = BinarySerializer.DeSerializeColor(compressedData, ref compressedOffset, compressedLength); logMessage += $", Text={data.Text}, TextColour={data.TextColour}"; } if ((compressedFlags & CompressedFlags.MediaURL) != 0) { data.MediaUrl = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); logMessage += $", MediaUrl={data.MediaUrl}"; } if ((compressedFlags & CompressedFlags.HasParticles) != 0) { // TODO: Parse the particle system data. OpenMetaverse says that this is a BitPack of 86 bytes. len = 86; compressedOffset += (int)len; logMessage += $", ParticleSystem({len})"; } data.ExtraParameters = BinarySerializer.DeSerializeExtraParameters(compressedData, ref compressedOffset, compressedOffset + compressedLength); if ((compressedFlags & CompressedFlags.HasSound) != 0) { data.SoundId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); data.Gain = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.SoundFlags = (SoundFlags)compressedData[compressedOffset++]; data.Radius = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, compressedLength); logMessage += $", SoundId={data.SoundId}, Gain={data.Gain}, SoundFlags={data.SoundFlags}, Radius={data.Radius}"; } if ((compressedFlags & CompressedFlags.HasNameValues) != 0) { data.NameValue = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); logMessage += $", NameValue={data.NameValue}"; } data.PathCurve = (PathType)compressedData[compressedOffset++]; data.PathBegin = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.PathEnd = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.PathScaleX = compressedData[compressedOffset++] * SCALE_QUANTA; data.PathScaleY = compressedData[compressedOffset++] * SCALE_QUANTA; data.PathShearX = compressedData[compressedOffset++] * SHEAR_QUANTA; data.PathShearY = compressedData[compressedOffset++] * SHEAR_QUANTA; data.PathTwist = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathTwistBegin = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathRadiusOffset = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathTaperX = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA; data.PathTaperY = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA; data.PathRevolutions = compressedData[compressedOffset++] * REV_QUANTA; data.PathSkew = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.ProfileCurve = (ProfileType)compressedData[compressedOffset++]; data.ProfileBegin = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.ProfileEnd = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.ProfileHollow = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * HOLLOW_QUANTA; data.TextureEntry = BinarySerializer.DeSerializeTextureEntry(compressedData, ref compressedOffset, compressedLength, true); logMessage += $", TextureEntry={data.TextureEntry}"; if ((compressedFlags & CompressedFlags.TextureAnimation) != 0) { data.TextureAnimation = BinarySerializer.DeSerializeTextureAnimation(compressedData, ref compressedOffset, compressedLength); logMessage += ", TextureAnimation"; } data.IsAttachment = (compressedFlags & CompressedFlags.HasNameValues) != 0 && data.ParentId != 0; } //Logger.LogDebug("ObjectUpdateCompressedMessage.DeSerialise", logMessage); }
/// <summary> /// See interface docs. /// </summary> /// <param name="buffer"></param> /// <returns></returns> public BaseStationMessage Decompress(byte[] buffer) { BaseStationMessage result = null; if (buffer.Length > 0 && (int)buffer[0] == buffer.Length) { using (var stream = new MemoryStream(buffer)) { using (var reader = new BinaryReader(stream)) { result = new BaseStationMessage(); stream.ReadByte(); // <-- length of buffer, we can skip this ushort checksum = reader.ReadUInt16(); // the checksum is skipped as well - caller should ensure it's a valid message before decompression result.MessageType = BaseStationMessageType.Transmission; result.StatusCode = BaseStationStatusCode.None; result.TransmissionType = (BaseStationTransmissionType)stream.ReadByte(); result.Icao24 = DecodeIcao(stream); result.MessageGenerated = result.MessageLogged = DateTime.Now; OptionalFields optionalFields = (OptionalFields)DecodeShort(reader); if ((optionalFields & OptionalFields.CallSign) != 0) { result.Callsign = DecodeString(stream); } if ((optionalFields & OptionalFields.Altitude) != 0) { result.Altitude = (int)DecodeFloatInt(stream); } if ((optionalFields & OptionalFields.GroundSpeed) != 0) { result.GroundSpeed = (int)DecodeFloatShort(reader); } if ((optionalFields & OptionalFields.Track) != 0) { result.Track = DecodeFloatShort(reader) / 10f; } if ((optionalFields & OptionalFields.Latitude) != 0) { result.Latitude = DecodeFloat(reader); } if ((optionalFields & OptionalFields.Longitude) != 0) { result.Longitude = DecodeFloat(reader); } if ((optionalFields & OptionalFields.VerticalRate) != 0) { result.VerticalRate = (int)DecodeFloatShort(reader); } if ((optionalFields & OptionalFields.Squawk) != 0) { result.Squawk = DecodeShort(reader); } bool hasSquawkHasChanged = (optionalFields & OptionalFields.SquawkHasChanged) != 0; bool hasEmergency = (optionalFields & OptionalFields.Emergency) != 0; bool hasIdentActive = (optionalFields & OptionalFields.IdentActive) != 0; bool hasOnGround = (optionalFields & OptionalFields.OnGround) != 0; if (hasSquawkHasChanged || hasEmergency || hasIdentActive || hasOnGround) { CompressedFlags flags = (CompressedFlags)stream.ReadByte(); if (hasSquawkHasChanged) { result.SquawkHasChanged = (flags & CompressedFlags.SquawkHasChanged) != 0; } if (hasEmergency) { result.Emergency = (flags & CompressedFlags.Emergency) != 0; } if (hasIdentActive) { result.IdentActive = (flags & CompressedFlags.IdentActive) != 0; } if (hasOnGround) { result.OnGround = (flags & CompressedFlags.OnGround) != 0; } } } } } return(result); }
protected override void DeSerialise(byte[] buf, ref int o, int length) { RegionHandle = new RegionHandle(BinarySerializer.DeSerializeUInt64_Le(buf, ref o, length)); TimeDilation = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length); string logMessage = $"ObjectUpdateCompressed: RegionHandle={RegionHandle}, TimeDilation={TimeDilation}"; int nObjects = buf[o++]; for (int i = 0; i < nObjects; i++) { UInt32 len; ObjectUpdateMessage.ObjectData data = new ObjectUpdateMessage.ObjectData(); Objects.Add(data); data.UpdateFlags = (ObjectUpdateFlags)BinarySerializer.DeSerializeUInt32_Le(buf, ref o, length); int compressedLength = BinarySerializer.DeSerializeUInt16_Le(buf, ref o, length); byte[] compressedData = new byte[compressedLength]; Array.Copy(buf, o, compressedData, 0, compressedLength); o += compressedLength; int compressedOffset = 0; logMessage += $"\n Object {i}: UpdateFlags={data.UpdateFlags}, Data({compressedData.Length})={BitConverter.ToString(compressedData)}"; data.FullId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); data.LocalId = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.PCode = (PCode)compressedData[compressedOffset++]; data.State = compressedData[compressedOffset++]; data.Crc = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.Material = (MaterialType)compressedData[compressedOffset++]; data.ClickAction = (ClickAction)compressedData[compressedOffset++]; data.Scale = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); data.Position = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); data.Rotation = BinarySerializer.DeSerializeQuaternion(compressedData, ref compressedOffset, compressedLength); CompressedFlags compressedFlags = (CompressedFlags)BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.OwnerId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); logMessage += $"\n FullId={data.FullId}, LocalId={data.LocalId}, PCode={data.PCode}, State={data.State}, Crc={data.Crc}, Material={data.Material}, ClickAction={data.ClickAction}, Scale={data.Scale}, Position={data.Position}, Rotation={data.Rotation}, CompressedFlags=({compressedFlags})"; if ((compressedFlags & CompressedFlags.HasAngularVelocity) != 0) { data.AngularVelocity = BinarySerializer.DeSerializeVector3(compressedData, ref compressedOffset, compressedLength); logMessage += $", AngularVelocity={data.AngularVelocity}"; } data.ParentId = (compressedFlags & CompressedFlags.HasParent) != 0 ? BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength) : (uint)0; logMessage += $", ParentId={data.ParentId}"; if ((compressedFlags & CompressedFlags.Tree) != 0) { byte treeSpecies = compressedData[compressedOffset++]; logMessage += $", TreeSpecies={treeSpecies}"; } if ((compressedFlags & CompressedFlags.ScratchPad) != 0) { len = compressedData[compressedOffset++]; compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32 logMessage += $", Scratchpad({len})"; } if ((compressedFlags & CompressedFlags.HasText) != 0) { data.Text = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); data.TextColour = BinarySerializer.DeSerializeColor(compressedData, ref compressedOffset, compressedLength); logMessage += $", Text={data.Text}, TextColour={data.TextColour}"; } if ((compressedFlags & CompressedFlags.MediaURL) != 0) { data.MediaUrl = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); logMessage += $", MediaUrl={data.MediaUrl}"; } if ((compressedFlags & CompressedFlags.HasParticles) != 0) { len = 86; logMessage += $", ParticleSystem({len})"; } byte nExtraParameters = compressedData[compressedOffset++]; for (int j = 0; j < nExtraParameters; j++) { if (j == 0) { logMessage += ", ExtraParameters=("; } ExtraParamType type = (ExtraParamType)BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, compressedLength); len = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); switch (type) { case ExtraParamType.Flexible: break; case ExtraParamType.Light: break; case ExtraParamType.Sculpt: break; case ExtraParamType.LightImage: break; case ExtraParamType.Mesh: break; default: throw new ArgumentOutOfRangeException(); } logMessage += $"{type}, "; compressedOffset += (int)len; // TODO: These offsets and length should all be UInt32 if (j == nExtraParameters - 1) { logMessage += ")"; } } if ((compressedFlags & CompressedFlags.HasSound) != 0) { data.SoundId = BinarySerializer.DeSerializeGuid(compressedData, ref compressedOffset, compressedLength); data.Gain = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, compressedLength); data.SoundFlags = (SoundFlags)compressedData[compressedOffset++]; data.Radius = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, compressedLength); logMessage += $", SoundId={data.SoundId}, Gain={data.Gain}, SoundFlags={data.SoundFlags}, Radius={data.Radius}"; } if ((compressedFlags & CompressedFlags.HasNameValues) != 0) { data.NameValue = BinarySerializer.DeSerializeString(compressedData, ref compressedOffset, compressedLength, 0); logMessage += $", NameValue={data.NameValue}"; } data.PathCurve = (PathType)compressedData[compressedOffset++]; data.PathBegin = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.PathEnd = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.PathScaleX = compressedData[compressedOffset++] * SCALE_QUANTA; data.PathScaleY = compressedData[compressedOffset++] * SCALE_QUANTA; data.PathShearX = compressedData[compressedOffset++] * SHEAR_QUANTA; data.PathShearY = compressedData[compressedOffset++] * SHEAR_QUANTA; data.PathTwist = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathTwistBegin = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathRadiusOffset = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.PathTaperX = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA; data.PathTaperY = (sbyte)compressedData[compressedOffset++] * TAPER_QUANTA; data.PathRevolutions = compressedData[compressedOffset++] * REV_QUANTA; data.PathSkew = (sbyte)compressedData[compressedOffset++] * SCALE_QUANTA; data.ProfileCurve = (ProfileType)compressedData[compressedOffset++]; data.ProfileBegin = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.ProfileEnd = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * CUT_QUANTA; data.ProfileHollow = BinarySerializer.DeSerializeUInt16_Le(compressedData, ref compressedOffset, length) * HOLLOW_QUANTA; UInt32 textureEntryLength = BinarySerializer.DeSerializeUInt32_Le(compressedData, ref compressedOffset, length); logMessage += $", textures({textureEntryLength})"; compressedOffset += (int)textureEntryLength; if ((compressedFlags & CompressedFlags.TextureAnimation) != 0) { TextureAnimation textureAnimation = new TextureAnimation() { Mode = (TextureAnimationMode)compressedData[compressedOffset++], Face = compressedData[compressedOffset++], SizeX = compressedData[compressedOffset++], SizeY = compressedData[compressedOffset++], Start = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length), Length = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length), Rate = BinarySerializer.DeSerializeFloat_Le(compressedData, ref compressedOffset, length) }; logMessage += ", TextureAnimation"; } data.IsAttachment = (compressedFlags & CompressedFlags.HasNameValues) != 0 && data.ParentId != 0; } //Logger.LogDebug(logMessage); }