Commit e372eccd authored by Dag Brattli's avatar Dag Brattli

Harden parser by parsing type again for the various messages

parent 022de558
......@@ -25,8 +25,10 @@ namespace NcaAisFeed
while ((line = reader.ReadLine()) != null) {
stopWatch.Reset();
stopWatch.Start();
var result = parser.TryParse(line, out AisResult aisResult);
if (!result) continue;
switch (aisResult.Type)
{
case 1:
......
......@@ -2,12 +2,13 @@
![coverage](https://gitlab.com/dbrattli/AisParser/badges/master/coverage.svg)
[NAisParser](https://gitlab.com/dbrattli/NAisParser) is an NMEA marine Automatic Identification
System (AIS) [AIVDM/AIVDO](http://catb.org/gpsd/AIVDM.html) decoder for .NET Standard. Written in F#
using [FParsec](http://www.quanttec.com/fparsec/). The main advantage of using a parser combinator
library such as FParsec, and using an applicative (functor) style is that the implementation looks
very similar to the specification, thus the code is clean, and easy to extend and validate against
the specification.
[NAisParser](https://gitlab.com/dbrattli/NAisParser) is a marine Automatic Identification
System (AIS) [AIVDM/AIVDO](http://catb.org/gpsd/AIVDM.html) NMEA message decoder for .NET Standard.
The library is Written in F# using [FParsec](http://www.quanttec.com/fparsec/). The main advantage of
using a parser combinator library such as FParsec, and using an applicative (functor) style is that
the implementation looks very similar to the specification, thus the code is clean, and easy to extend
and validate against the specification.
Currently parses:
......@@ -49,7 +50,7 @@ var result2 = parser.TryParse(line2, out AisResult aisResult); // Returns true
If an error occurs while parsing `TryParse` will raise an `ArgumentException`.
In the second stage you parse the AIS payload data depending on the type of message. The type of
message is available in the 'Type' property of a valid `AisResult` from stage 1. To parse a message
message is available in the `Type` property of a valid `AisResult` from stage 1. To parse a message
of type 1 you call `TryParse` with an `out` parameter of type `MessageType123`. To parse a message
of type 5 you call `TryParse` with an `out` parameter of type `MessageType5`.
......
......@@ -27,7 +27,7 @@ type public Parser() =
/// <exception cref="System.ArgumentException">Thrown if the data
/// packet is invalid and parsing of the packet failed.
/// </exception>
member public this.TryParse(input: String, [<Out>] result : AisResult byref) : bool =
member public _this.TryParse(input: String, [<Out>] result : AisResult byref) : bool =
let res = run aisParser input
match res with
......@@ -40,7 +40,7 @@ type public Parser() =
let fragment = fragments |> Seq.reduce defragment
match fragment with
| Success (c, _, _) ->
result <- { c with Type = byte c.Payload.[0]; Payload = List.tail c.Payload }
result <- { c with Type = byte c.Payload.[0]; Payload = c.Payload }
fragments.Clear()
true
| Failure (error, _, _) ->
......@@ -64,7 +64,7 @@ type public Parser() =
/// payload is invalid and parsing of the packet failed.
/// </exception>
member public this.TryParse(input: AisResult, [<Out>] result : MessageType123 byref) : bool =
member public _this.TryParse(input: AisResult, [<Out>] result : MessageType123 byref) : bool =
let binaryString = Common.intListToBinaryString input.Payload
let res = run Type123.parseMessageType123 binaryString
......@@ -95,7 +95,7 @@ type public Parser() =
/// payload is invalid and parsing of the packet failed.
/// </exception>
member public this.TryParse(input: AisResult, [<Out>] result : MessageType5 byref) : bool =
member public _this.TryParse(input: AisResult, [<Out>] result : MessageType5 byref) : bool =
let binaryString = Common.intListToBinaryString input.Payload
let res = run Type5.parseMessageType5 binaryString
......
......@@ -4,7 +4,7 @@ open System
open FParsec
type NavigationStatus =
| UnderWayUsingEngine=0
| UnderWayUsingEngine = 0
| AtAnchor = 1
| NotUnderCommand = 2
| RestrictedManoeuverability = 3
......@@ -74,6 +74,7 @@ type ManeuverIndicator =
// Common Navigation Block
type MessageType123 = {
Type: byte;
Repeat: byte;
Mmsi: int;
Status: NavigationStatus;
......@@ -124,15 +125,13 @@ module Common =
// Allowed characters in payload
let allowedChars = List.map char [48..119]
let toPaddedBinary' (i: int) =
Convert.ToString (i, 2) |> int |> sprintf "%06d"
let toPaddedBinary (i: int) =
// Convert.ToString (i, 2) |> int |> sprintf "%06d"
let str = Convert.ToString (i, 2)
let pad = String.replicate (6-str.Length) "0"
pad + str
let char2int chr =
let char2int (chr: char) =
let value = int chr
if value > 40 then
let n = value - 48
......@@ -149,10 +148,13 @@ module Common =
String.concat "" binList
let parseType : Parser<_> =
anyOf allowedChars
|>> (char2int >> toPaddedBinary)
|>> fun x -> Convert.ToByte(x, 2) // Map back to byte
let parseType (type': string) =
pstring type'
|>> fun x -> Convert.ToByte(x, 2)
let parseType3 (type1: string) (type2: string) (type3: string)=
pstring type1 <|> pstring type2 <|> pstring type3
|>> fun x -> Convert.ToByte(x, 2)
let parseRepeat = Core.parseUint2
......
......@@ -44,7 +44,7 @@ module Core =
| "001110" -> "N" | "011110" -> "^" | "101110" -> "." | "111110" -> ">"
| "001111" -> "O" | "011111" -> "_" | "101111" -> "/" | _ -> "?"
let inline parseAscii count =
let parseAscii count =
let chars = count / 6
let reducer x y =
......@@ -57,16 +57,12 @@ module Core =
ps |>> fun x -> x.Trim([| ' '; '@'|])
let inline apply' pf pa =
pf >>= fun f ->
pa >>= fun a ->
preturn (f a)
let inline apply pf pa =
// pf >>= fun f -> pa >>= fun a -> preturn (f a)
pf >>= fun f -> pa |>> f
let inline (<*>) f a = apply f a
let inline ( *>) x y = x >>. y
let inline ( *>) pf pa = pa >>. pf
let inline (<* ) x y = x .>> y
\ No newline at end of file
let inline (<* ) pf pa = pf .>> pa
\ No newline at end of file
......@@ -7,9 +7,10 @@ open NAisParser.Core
module Type123 =
let messageType123 repeat mmsi status turn
let messageType123 type' repeat mmsi status turn
speed accuracy lon lat course heading second maneuver: MessageType123=
{
Type = type'
Repeat = repeat;
Mmsi = mmsi;
Status = status;
......@@ -25,6 +26,7 @@ module Type123 =
}
let defaultMessageType123: MessageType123= {
Type = 0uy;
Repeat = 0uy;
Mmsi = 0;
Status = NavigationStatus.NotDefined;
......@@ -88,8 +90,12 @@ module Type123 =
let value = Convert.ToInt32(x, 2)
enum<ManeuverIndicator>(value)
let parseType123 : Parser<_> =
Common.parseType3 "000001" "000010" "000011"
let parseMessageType123: Parser<_> =
preturn messageType123
<*> parseType123
<*> Common.parseRepeat
<*> Common.parseMmsi
<*> parseStatus
......
......@@ -112,8 +112,11 @@ module Type5 =
let parseDte = Core.parseBits 1 |>> fun x -> Convert.ToByte(x, 2) = 1uy
let parseType5 = Common.parseType "000101"
let parseMessageType5: Parser<MessageType> =
preturn messageType5
*> parseType5
<*> Common.parseRepeat
<*> Common.parseMmsi
<*> parseVersion
......
......@@ -5,6 +5,7 @@
<ItemGroup>
<Compile Include="AisTests.fs" />
<Compile Include="Type123Tests.fs" />
<Compile Include="Type4Tests.fs" />
<Compile Include="Type5Tests.fs" />
<Compile Include="ApiTests.fs" />
<Compile Include="Program.fs" />
......
......@@ -7,14 +7,14 @@ open FParsec
open NAisParser
[<TestClass>]
type TestClassTest123 () =
type TestClassType123 () =
[<SetUp>]
member this.Setup () =
member _this.Setup () =
()
[<Test>]
member this.TestParseType1MessageIsSuccesss () =
member _this.TestParseType1MessageIsSuccesss () =
// Arrage
let input = "!BSVDM,1,1,,A,13mAwp001m0MMrjSoomG6mWT0<1h,0*16"
......
namespace Tests
open NUnit.Framework
open FsUnit
open FParsec
open NAisParser
open NAisParser.Ais
[<TestClass>]
type TestClassType4 () =
[<SetUp>]
member _this.Setup () =
()
[<Test>]
member _this.TestParseType1MessageIsSuccesss () =
// Arrage
let input = "!AIVDM,1,1,,A,400TcdiuiT7VDR>3nIfr6>i00000,0*78"
// Act
let result = run Ais.aisParser input
let result2 =
match result with
| Success (ais, _, _) ->
run Type123.parseMessageType123 (Common.intListToBinaryString ais.Payload)
| Failure (a, b, c) -> Failure(a, b, c)
// Assert
isSuccess(result) |> should be True
isSuccess(result2) |> should be False
......@@ -38,7 +38,7 @@ type TestClassType5 () =
result2 |> isSuccess |> should be True
[<Test>]
member _this.``Test BSVDM multi fragment with invalid packet is failure`` () =
member _this.``Test BSVDM multi fragment packets with invalid first packet is failure`` () =
// Arrage
let input: string [] = [|
"!XXXXX,2,1,2,A,53mDDD02>EjthmLJ220HtppE>2222222222222164@G:34rdR?QSkSQDp888,0*15";
......@@ -58,3 +58,25 @@ type TestClassType5 () =
// Assert
result |> isSuccess |> should be False
[<Test>]
member _this.``Test BSVDM multi fragment packets with invalid second packet is failure`` () =
// Arrage
let input: string [] = [|
"!BSVDM,2,1,2,A,53mDDD02>EjthmLJ220HtppE>2222222222222164@G:34rdR?QSkSQDp888,0*15";
"!XXXXX,2,2,2,A,88888888880,2*3F";
|]
let result =
input
|> Seq.map (run aisParser)
|> Seq.reduce defragment
let result2 =
match result with
| Success (ais, _, _) ->
run Type5.parseMessageType5 (Common.intListToBinaryString ais.Payload)
| Failure (a, b, c) ->
Failure(a, b, c)
// Assert
result |> isSuccess |> should be False
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