Commit 2efdb0a3 authored by Dag Brattli's avatar Dag Brattli

Merge branch 'master' of gitlab.com:dbrattli/NAisParser

parents 5d7b255d 662eef45
......@@ -72,10 +72,14 @@
<!-- Do a global restore if required -->
<Exec Command='$(PaketBootStrapperCommand)' Condition="Exists('$(PaketBootStrapperExePath)') AND !(Exists('$(PaketExePath)'))" ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --target-framework "$(TargetFrameworks)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' == '' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --target-framework "$(TargetFramework)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' != '' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore' Condition=" '$(PaketRestoreRequired)' == 'true' " ContinueOnError="false" />
<!-- Step 2 Detect project specific changes -->
<ItemGroup>
<MyTargetFrameworks Condition="'$(TargetFramework)' != '' " Include="$(TargetFramework)"></MyTargetFrameworks>
<MyTargetFrameworks Condition="'@(TargetFrameworks)' != '' " Include="$(TargetFrameworks)"></MyTargetFrameworks>
<PaketResolvedFilePaths Include="@(MyTargetFrameworks -> '$(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).%(Identity).paket.resolved')"></PaketResolvedFilePaths>
</ItemGroup>
<PropertyGroup>
<PaketReferencesCachedFilePath>$(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).paket.references.cached</PaketReferencesCachedFilePath>
<!-- MyProject.fsproj.paket.references has the highest precedence -->
......@@ -84,7 +88,10 @@
<PaketOriginalReferencesFilePath Condition=" !Exists('$(PaketOriginalReferencesFilePath)')">$(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references</PaketOriginalReferencesFilePath>
<!-- paket.references -->
<PaketOriginalReferencesFilePath Condition=" !Exists('$(PaketOriginalReferencesFilePath)')">$(MSBuildProjectDirectory)\paket.references</PaketOriginalReferencesFilePath>
<PaketResolvedFilePath>$(MSBuildProjectDirectory)\obj\$(MSBuildProjectFile).$(TargetFramework).paket.resolved</PaketResolvedFilePath>
<DoAllResolvedFilesExist>false</DoAllResolvedFilesExist>
<DoAllResolvedFilesExist Condition="Exists(%(PaketResolvedFilePaths.Identity))">true</DoAllResolvedFilesExist>
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequiredReason>references-file-or-cache-not-found</PaketRestoreRequiredReason>
</PropertyGroup>
......@@ -103,24 +110,29 @@
</PropertyGroup>
<!-- Step 2 b detect relevant changes in project file (new targetframework) -->
<PropertyGroup Condition=" !Exists('$(PaketResolvedFilePath)') AND '$(TargetFramework)' != '' ">
<PropertyGroup Condition=" '$(DoAllResolvedFilesExist)' != 'true' ">
<PaketRestoreRequired>true</PaketRestoreRequired>
<PaketRestoreRequiredReason>target-framework '$(TargetFramework)'</PaketRestoreRequiredReason>
<PaketRestoreRequiredReason>target-framework '$(TargetFramework)' or '$(TargetFrameworks)'</PaketRestoreRequiredReason>
</PropertyGroup>
<!-- Step 3 Restore project specific stuff if required -->
<Message Condition=" '$(PaketRestoreRequired)' == 'true' " Importance="low" Text="Detected a change ('$(PaketRestoreRequiredReason)') in the project file '$(MSBuildProjectFullPath)', calling paket restore" />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)"' Condition=" '$(PaketRestoreRequired)' == 'true' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --target-framework "$(TargetFrameworks)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' == '' " ContinueOnError="false" />
<Exec Command='$(PaketCommand) restore --project "$(MSBuildProjectFullPath)" --target-framework "$(TargetFramework)"' Condition=" '$(PaketRestoreRequired)' == 'true' AND '$(TargetFramework)' != '' " ContinueOnError="false" />
<!-- This shouldn't actually happen, but just to be sure. -->
<Error Condition=" !Exists('$(PaketResolvedFilePath)') AND '$(TargetFramework)' != '' AND '$(ResolveNuGetPackages)' != 'False' " Text="Paket file '$(PaketResolvedFilePath)' is missing while restoring $(MSBuildProjectFile). Please delete 'paket-files/paket.restore.cached' and call 'paket restore'." />
<PropertyGroup>
<DoAllResolvedFilesExist>false</DoAllResolvedFilesExist>
<DoAllResolvedFilesExist Condition="Exists(%(PaketResolvedFilePaths.Identity))">true</DoAllResolvedFilesExist>
</PropertyGroup>
<Error Condition=" '$(DoAllResolvedFilesExist)' != 'true' AND '$(ResolveNuGetPackages)' != 'False' " Text="One Paket file '@(PaketResolvedFilePaths)' is missing while restoring $(MSBuildProjectFile). Please delete 'paket-files/paket.restore.cached' and call 'paket restore'." />
<!-- Step 4 forward all msbuild properties (PackageReference, DotNetCliToolReference) to msbuild -->
<ReadLinesFromFile Condition="Exists('$(PaketResolvedFilePath)')" File="$(PaketResolvedFilePath)" >
<ReadLinesFromFile Condition="'@(PaketResolvedFilePaths)' != ''" File="%(PaketResolvedFilePaths.Identity)" ><!--Condition="Exists('%(PaketResolvedFilePaths.Identity)')"-->
<Output TaskParameter="Lines" ItemName="PaketReferencesFileLines"/>
</ReadLinesFromFile>
<ItemGroup Condition=" Exists('$(PaketResolvedFilePath)') AND '@(PaketReferencesFileLines)' != '' " >
<ItemGroup Condition=" '@(PaketReferencesFileLines)' != '' " >
<PaketReferencesFileLinesInfo Include="@(PaketReferencesFileLines)" >
<PackageName>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])</PackageName>
<PackageVersion>$([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])</PackageVersion>
......
......@@ -68,8 +68,8 @@ If result is `true` then you have a valid `MessageType123` such as:
RateOfTurn = 0.0;
SpeedOverGround = 117;
PositionAccuracy = 0;
Longitude = 62.69262167;
Latitude = 6.437268333;
Latitude = 62.69262167;
Longitude = 6.437268333;
CourseOverGround = 181.9;
TrueHeading = 179;
TimeStamp = 50;
......
......@@ -7,6 +7,10 @@ open System.Runtime.InteropServices //for OutAttribute
open FParsec
open NAisParser.Ais
type MessageType =
| Type123 of MessageType123
| Type4 of MessageType4
| Type5 of MessageType5
type public Parser() =
let fragments = new List<ParserResult<AisResult, unit>>()
......@@ -70,12 +74,8 @@ type public Parser() =
match res with
| Success (message, _, _) ->
match message with
| Type123 cnb ->
result <- cnb
true
| _ ->
false
result <- message
true
| Failure (error, _, _) ->
raise (System.ArgumentException(error))
......@@ -88,8 +88,7 @@ type public Parser() =
/// A MessageType5 that will be set if the method returns true.
/// </param>
/// <returns>
/// True if parsing of the packet completed. False if this is not
/// a message of type 4.
/// True if parsing of the packet completed.
/// </returns>
/// <exception cref="System.ArgumentException">Thrown if the data
/// payload is invalid and parsing of the packet failed.
......@@ -101,12 +100,8 @@ type public Parser() =
match res with
| Success (message, _, _) ->
match message with
| Type4 cnb ->
result <- cnb
true
| _ ->
false
result <- message
true
| Failure (error, _, _) ->
raise (System.ArgumentException(error))
......@@ -119,8 +114,7 @@ type public Parser() =
/// A MessageType5 that will be set if the method returns true.
/// </param>
/// <returns>
/// True if parsing of the packet completed. False if this is not
/// a message of type 5.
/// True if parsing of the packet completed.
/// </returns>
/// <exception cref="System.ArgumentException">Thrown if the data
/// payload is invalid and parsing of the packet failed.
......@@ -132,11 +126,7 @@ type public Parser() =
match res with
| Success (message, _, _) ->
match message with
| Type5 cnb ->
result <- cnb
true
| _ ->
false
result <- message
true
| Failure (error, _, _) ->
raise (System.ArgumentException(error))
......@@ -72,76 +72,6 @@ type ManeuverIndicator =
| NoSpecialManeuver = 0
| SpecialManeuver = 1
// Common Navigation Block
type MessageType123 = {
Type: byte;
Repeat: byte;
Mmsi: int;
Status: NavigationStatus;
RateOfTurn: float;
SpeedOverGround: int;
PositionAccuracy: bool;
Longitude: float;
Latitude: float;
CourseOverGround: float;
TrueHeading: int;
TimeStamp: int;
ManeuverIndicator: ManeuverIndicator;
RaimFlag: bool;
RadioStatus: int
}
// Base Station Report
type MessageType4 = {
Repeat: byte;
Mmsi: int;
Year: int;
Month: int;
Day: int;
Hour: int;
Minute: int;
Second: int;
FixQuality: bool;
Longitude: float;
Latitude: float;
Epfd: EpfdFixType;
RaimFlag: bool;
RadioStatus: int
}
// Static And Voyage Related Data
type MessageType5 = {
Repeat: byte;
Mmsi: int;
Version: byte;
ImoNumber: int;
CallSign: string;
VesselName: string;
ShipType: ShipType;
ToBow: int;
ToStern: int;
ToPort: int;
ToStarBoard: int;
Epfd: EpfdFixType;
Month: int;
Day: int;
Hour: int;
Minute: int;
Draught: float;
Destination: string;
Dte: bool;
}
type BaseStationReport = {
Repeat: byte;
Mmsi: int;
}
type MessageType =
| Type123 of MessageType123
| Type4 of MessageType4
| Type5 of MessageType5
module Common =
// Allowed characters in payload
let allowedChars = List.map char [48..119]
......@@ -179,9 +109,9 @@ module Common =
<|> pstring type3
|>> fun x -> Convert.ToByte(x, 2)
let parseRepeat = Core.parseUint2
let parseRepeat = Core.parseByteN 2
let parseMmsi = Core.parseUint30
let parseMmsi = Core.parseIntN 30
let parseLongitude =
Core.parseBits 28
......@@ -196,18 +126,11 @@ module Common =
|>> fun x -> float(x) / 600000.0
let parseEpfd =
Core.parseBits 4
|>> (fun x ->
let value = Convert.ToInt32(x, 2)
enum<EpfdFixType>(value)
)
Core.parseIntN 4
|>> fun x -> enum<EpfdFixType>(x)
let parseSpare =
Core.parseBits 3
let parseSpare = Core.parseBits 3
let parseRaimFlag =
Core.parseBool
let parseRaimFlag = Core.parseBool
let parseRadioStatus =
Core.parseBits 19
|>> fun x -> Convert.ToInt32(x, 2)
\ No newline at end of file
let parseRadioStatus = Core.parseIntN 19
......@@ -17,14 +17,14 @@ module Core =
parseBits 1
|>> fun x -> Convert.ToByte(x, 2) = 1uy
let parseUint2 =
parseBits 2
|>> fun x -> Convert.ToByte(x, 2)
let parseUint30 =
parseBits 30
let parseIntN bits =
parseBits bits
|>> fun x -> Convert.ToInt32(x, 2)
let parseByteN bits =
parseBits bits
|>> fun x -> Convert.ToByte(x, 2)
let parseSByte =
parseBits 8
|>> fun x -> Convert.ToSByte(x, 2)
......
......@@ -5,8 +5,27 @@ open FParsec
open NAisParser.Core
// Common Navigation Block
type MessageType123 = {
Type: byte;
Repeat: byte;
Mmsi: int;
Status: NavigationStatus;
RateOfTurn: float;
SpeedOverGround: int;
PositionAccuracy: bool;
Longitude: float;
Latitude: float;
CourseOverGround: float;
TrueHeading: int;
TimeStamp: int;
ManeuverIndicator: ManeuverIndicator;
RaimFlag: bool;
RadioStatus: int
}
module Type123 =
let messageType123 type' repeat mmsi status turn speed accuracy lon
lat course heading second maneuver raim radio: MessageType123=
{
......@@ -50,42 +69,29 @@ module Type123 =
Core.parseSByte
|>> fun x -> squareSigned((float x) / 4.733)
let parseSpeedOverGround =
Core.parseBits 10
|>> fun x -> Convert.ToInt32(x, 2)
let parseSpeedOverGround = Core.parseIntN 10
let parsePositionAccuracy =
Core.parseBool
let parsePositionAccuracy = Core.parseBool
let parseCourseOverGround =
Core.parseBits 12
|>> fun x -> Convert.ToInt32(x, 2)
Core.parseIntN 12
|>> fun x -> float(x) / 10.0
let parseStatus =
Core.parseBits 4
|>> (fun x ->
let value = Convert.ToInt32(x, 2)
enum<NavigationStatus>(value)
)
Core.parseIntN 4
|>> fun x -> enum<NavigationStatus>(x)
let parseTrueHeading =
Core.parseBits 9
|>> fun x -> Convert.ToInt32(x, 2)
let parseTrueHeading = Core.parseIntN 9
let parseTimeStamp =
Core.parseBits 6
|>> fun x -> Convert.ToInt32(x, 2)
let parseTimeStamp = Core.parseIntN 6
let parseManeuverIndicator =
Core.parseBits 2
|>> fun x ->
let value = Convert.ToInt32(x, 2)
enum<ManeuverIndicator>(value)
Core.parseIntN 2
|>> fun x -> enum<ManeuverIndicator>(x)
let parseType123 : Parser<_> =
// A little repetitive, but better to do it here at declaration time
Common.parseType3 (Common.toPaddedBinary 1) (Common.toPaddedBinary 2) (Common.toPaddedBinary 3)
Common.parseType3 (Common.toPaddedBinary 1) (Common.toPaddedBinary 2) (Common.toPaddedBinary 3)
let parseMessageType123: Parser<_> =
preturn messageType123
......@@ -104,5 +110,4 @@ module Type123 =
<*> parseManeuverIndicator
<* Common.parseSpare
<*> Common.parseRaimFlag
<*> Common.parseRadioStatus
|>> Type123
\ No newline at end of file
<*> Common.parseRadioStatus
\ No newline at end of file
namespace NAisParser
open System
open FParsec
open NAisParser.Core
// Base Station Report
type MessageType4 = {
Repeat: byte;
Mmsi: int;
Year: int;
Month: int;
Day: int;
Hour: int;
Minute: int;
Second: int;
FixQuality: bool;
Longitude: float;
Latitude: float;
Epfd: EpfdFixType;
RaimFlag: bool;
RadioStatus: int
}
module Type4 =
let messageType4 repeat mmsi year month day hour minute second
accuracy lat lon epfd raim radio
......@@ -42,31 +59,23 @@ module Type4 =
RadioStatus = 0
}
let parseYear =
Core.parseBits 14
|>> fun x -> Convert.ToInt32(x, 2)
let parseYear = Core.parseIntN 14
let parseMonth =
Core.parseBits 4
|>> fun x -> Convert.ToInt32(x, 2)
let parseMonth = Core.parseIntN 4
let parseDay =
Core.parseBits 5
|>> fun x -> Convert.ToInt32(x, 2)
let parseDay = Core.parseIntN 5
let parseHour = parseDay
let parseHour = Core.parseIntN 5
let parseMinute =
Core.parseBits 6
|>> fun x -> Convert.ToInt32(x, 2)
let parseMinute = Core.parseIntN 6
let parseSecond = parseMinute
let parseSecond = Core.parseIntN 6
let parseFixQuality = Core.parseBool
let parseType4 = Common.parseType <| Common.toPaddedBinary 4
let parseMessageType4: Parser<MessageType> =
let parseMessageType4: Parser<_> =
preturn messageType4
*> parseType4
<*> Common.parseRepeat
......@@ -83,6 +92,4 @@ module Type4 =
<*> Common.parseEpfd
<* Common.parseSpare
<*> Common.parseRaimFlag
<*> Common.parseRadioStatus
|>> Type4
<*> Common.parseRadioStatus
\ No newline at end of file
namespace NAisParser
open System
open FParsec
open NAisParser.Core
// Static And Voyage Related Data
type MessageType5 = {
Repeat: byte;
Mmsi: int;
Version: byte;
ImoNumber: int;
CallSign: string;
VesselName: string;
ShipType: ShipType;
ToBow: int;
ToStern: int;
ToPort: int;
ToStarBoard: int;
Epfd: EpfdFixType;
Month: int;
Day: int;
Hour: int;
Minute: int;
Draught: float;
Destination: string;
Dte: bool;
}
module Type5 =
let messageType5 repeat mmsi version imo
callsign shipname shiptype tobow tostern toport tostarboard epfd
......@@ -53,53 +75,37 @@ module Type5 =
Dte = false;
}
let parseVersion =
Core.parseBits 2
|>> fun x -> Convert.ToByte(x, 2)
let parseVersion = Core.parseByteN 2
let parseImoNumber =
Core.parseBits 30
|>> fun x -> Convert.ToInt32(x, 2)
let parseImoNumber = Core.parseIntN 30
let parseShipType =
Core.parseBits 8
|>> fun x ->
let value = Convert.ToInt32(x, 2)
enum<ShipType>(value)
Core.parseIntN 8
|>> fun x -> enum<ShipType>(x)
let parseCallSign = Core.parseAscii 42
let parseVesselName = Core.parseAscii 120
let parseToBow =
Core.parseBits 9
|>> fun x -> Convert.ToInt32(x, 2)
let parseToBow = Core.parseIntN 9
let parseToStern = parseToBow
let parseToPort =
Core.parseBits 6
|>> fun x -> Convert.ToInt32(x, 2)
let parseToPort = Core.parseIntN 6
let parseToStarboard = parseToPort
let parseMonth =
Core.parseBits 4
|>> fun x -> Convert.ToInt32(x, 2)
let parseMonth = Core.parseIntN 4
let parseDay =
Core.parseBits 5
|>> fun x -> Convert.ToInt32(x, 2)
let parseDay = Core.parseIntN 5
let parseHour = parseDay
let parseHour = Core.parseIntN 5
let parseMinute =
Core.parseBits 6
|>> fun x -> Convert.ToInt32(x, 2)
let parseMinute = Core.parseIntN 6
let parseDraught =
Core.parseBits 8
|>> fun x -> (Convert.ToInt32(x, 2) |> float) / 10.0
Core.parseIntN 8
|>> fun x -> float x / 10.0
let parseDestination = Core.parseAscii 120
......@@ -107,7 +113,7 @@ module Type5 =
let parseType5 = Common.parseType <| Common.toPaddedBinary 5
let parseMessageType5: Parser<MessageType> =
let parseMessageType5: Parser<_> =
preturn messageType5
*> parseType5
<*> Common.parseRepeat
......@@ -129,5 +135,3 @@ module Type5 =
<*> parseDraught
<*> parseDestination
<*> parseDte
|>> Type5
NUGET
remote: https://api.nuget.org/v3/index.json
coverlet.msbuild (1.2)
coverlet.msbuild (2.0)
FParsec (1.0.3)
FSharp.Core (>= 4.0.0.1) - restriction: >= net40
FSharp.Core (>= 4.2.3) - restriction: && (< net40) (>= netstandard1.6)
......@@ -68,7 +68,7 @@ NUGET
System.Runtime.InteropServices.RuntimeInformation (>= 4.0) - restriction: >= uap10.0
Microsoft.NETCore.Platforms (2.0.2) - restriction: || (&& (>= dnxcore50) (>= netstandard1.6)) (&& (< monoandroid) (< monotouch) (< net45) (>= netstandard1.6) (< netstandard2.0) (< win8) (< wpa81) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (< monoandroid) (< net45) (< netstandard1.3) (>= netstandard1.6) (< win8) (< wpa81)) (&& (< monoandroid) (< net45) (< netstandard1.5) (>= netstandard1.6) (< win8) (< wpa81)) (&& (>= net45) (< netstandard1.3) (>= netstandard2.0)) (&& (< net45) (< netstandard1.2) (>= netstandard2.0) (< win8)) (&& (< net45) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< net46) (>= net461) (>= netstandard2.0)) (>= netcoreapp1.0) (&& (< netstandard1.0) (>= netstandard2.0) (< portable-net45+win8)) (&& (< netstandard1.0) (>= netstandard2.0) (>= win8)) (&& (< netstandard1.0) (>= netstandard2.0) (< win8)) (&& (< netstandard1.3) (>= netstandard2.0) (< win8) (>= wpa81)) (&& (>= netstandard2.0) (< portable-net45+win8+wpa81)) (&& (>= netstandard2.0) (>= uap10.0)) (&& (>= netstandard2.0) (>= wp8))
Microsoft.NETCore.Targets (2.0) - restriction: || (&& (< monoandroid) (< monotouch) (< net45) (>= netstandard1.6) (< netstandard2.0) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)) (&& (< net45) (< netstandard1.4) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.5) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.6) (>= netstandard2.0) (< win8) (< wpa81)) (>= netcoreapp1.0) (&& (>= netstandard2.0) (>= uap10.0))
Microsoft.TestPlatform.ObjectModel (15.7) - restriction: >= netcoreapp1.0
Microsoft.TestPlatform.ObjectModel (15.7.2) - restriction: >= netcoreapp1.0
NETStandard.Library (>= 1.6) - restriction: && (< net451) (>= netstandard1.5)
System.ComponentModel.EventBasedAsync (>= 4.0.11) - restriction: && (< net451) (>= netstandard1.5)
System.ComponentModel.TypeConverter (>= 4.1) - restriction: || (&& (< net451) (>= netstandard1.4) (< netstandard1.5)) (&& (< net451) (>= netstandard1.5))
......@@ -83,9 +83,9 @@ NUGET
System.Runtime.Serialization.Primitives (>= 4.1.1) - restriction: && (< net451) (>= netstandard1.5)
System.Threading.Thread (>= 4.0) - restriction: && (< net451) (>= netstandard1.5)
System.Xml.XPath.XmlDocument (>= 4.0.1) - restriction: && (< net451) (>= netstandard1.5)
Microsoft.TestPlatform.TestHost (15.7) - restriction: >= netcoreapp1.0
Microsoft.TestPlatform.TestHost (15.7.2) - restriction: >= netcoreapp1.0
Microsoft.Extensions.DependencyModel (>= 1.0.3) - restriction: >= netcoreapp1.0
Microsoft.TestPlatform.ObjectModel (>= 15.7) - restriction: || (>= netcoreapp1.0) (>= uap10.0)
Microsoft.TestPlatform.ObjectModel (>= 15.7.2) - restriction: || (>= netcoreapp1.0) (>= uap10.0)
Newtonsoft.Json (>= 9.0.1) - restriction: || (>= netcoreapp1.0) (>= uap10.0)
Microsoft.Win32.Primitives (4.3) - restriction: || (&& (< net45) (< netstandard1.4) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.5) (>= netstandard2.0) (< win8) (< wpa81)) (&& (< net45) (< netstandard1.6) (>= netstandard2.0) (< win8) (< wpa81)) (>= netcoreapp1.0) (&& (>= netstandard2.0) (>= uap10.0))
Microsoft.NETCore.Platforms (>= 1.1) - restriction: && (< monoandroid) (< monotouch) (< net46) (>= netstandard1.3) (< xamarinios) (< xamarinmac) (< xamarintvos) (< xamarinwatchos)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment