Browse Source

Move address parsing out into its own Ragel file.

pull/2/head
Justine Alexandra Roberts Tunney 11 years ago
parent
commit
5c5ab69bff
7 changed files with 7206 additions and 8781 deletions
  1. +1
    -86
      sip/addr.go
  2. +2776
    -0
      sip/addr_parse.go
  3. +213
    -0
      sip/addr_parse.rl
  4. +4
    -0
      sip/addr_test.go
  5. +4072
    -8603
      sip/msg_parse.go
  6. +66
    -87
      sip/msg_parse.rl
  7. +74
    -5
      sip/msg_test.go

+ 1
- 86
sip/addr.go View File

@ -20,9 +20,7 @@ package sip
import (
"bytes"
"errors"
"github.com/jart/gosip/util"
"strings"
)
// Represents a SIP Address Linked List
@ -33,90 +31,7 @@ type Addr struct {
Next *Addr // for comma separated lists of addresses
}
// Parses a SIP address.
func ParseAddr(s string) (addr *Addr, err error) {
addr = new(Addr)
l := len(s)
if l == 0 {
return nil, errors.New("empty addr")
}
// Extract display.
switch n := strings.IndexAny(s, "\"<"); {
case n < 0:
return nil, errors.New("Invalid address")
case s[n] == '<': // Display is not quoted.
addr.Display, s = strings.Trim(s[0:n], " "), s[n+1:]
case s[n] == '"': // We found an opening quote.
s = s[n+1:]
LOL:
for s != "" {
switch s[0] {
case '"': // Closing quote.
s = s[1:]
break LOL
case '\\': // Escape sequence.
if len(s) < 2 {
return nil, errors.New("Evil quote escape")
}
switch s[1] {
case '"':
addr.Display += "\""
case '\\':
addr.Display += "\\"
}
s = s[2:]
default: // Generic character.
addr.Display += string(s[0])
s = s[1:]
}
}
if s == "" {
return nil, errors.New("No closing quote in display")
}
for s != "" {
c := s[0]
s = s[1:]
if c == '<' {
break
}
}
}
if n := strings.Index(s, ">"); n > 0 {
addr.Uri, err = ParseURI(s[0:n])
if err != nil {
return nil, err
}
s = s[n+1:]
} else {
addr.Uri, err = ParseURI(s)
if err != nil {
return nil, err
}
s = ""
}
// Extract semicolon delimited params.
if s != "" && s[0] == ';' {
addr.Params = parseParams(s[1:])
s = ""
}
// Is there another address?
s = strings.TrimLeft(s, " \t")
if s != "" && s[0] == ',' {
s = strings.TrimLeft(s[1:], " \t")
if s != "" {
addr.Next, err = ParseAddr(s)
if err != nil {
return nil, err
}
}
}
return addr, nil
}
//go:generate ragel -Z -G2 -o addr_parse.go addr_parse.rl
func (addr *Addr) String() string {
if addr == nil {


+ 2776
- 0
sip/addr_parse.go
File diff suppressed because it is too large
View File


+ 213
- 0
sip/addr_parse.rl View File

@ -0,0 +1,213 @@
// -*-go-*-
package sip
import (
"errors"
"fmt"
"strings"
)
%% machine addr;
%% write data;
// ParseAddr turns a SIP address into a data structure.
func ParseAddr(s string) (addr *Addr, err error) {
if s == "" {
return nil, errors.New("Empty SIP message")
}
return ParseAddrBytes([]byte(s), nil)
}
// ParseAddr turns a SIP address slice into a data structure.
func ParseAddrBytes(data []byte, next *Addr) (addr *Addr, err error) {
if data == nil {
return nil, nil
}
addr = new(Addr)
result := addr
cs := 0
p := 0
pe := len(data)
eof := len(data)
buf := make([]byte, len(data))
amt := 0
mark := 0
var name string
%%{
action mark {
mark = p
}
action start {
amt = 0
}
action append {
buf[amt] = fc
amt++
}
action name {
name = string(data[mark:p])
}
action display {
addr.Display = strings.TrimRight(string(buf[0:amt]), " \t\r\n")
}
action qdisplay {
addr.Display = string(buf[0:amt])
}
action uri {
addr.Uri, err = ParseURIBytes(data[mark:p])
if err != nil { return nil, err }
}
action param {
if addr.Params == nil {
addr.Params = Params{}
}
addr.Params[name] = string(buf[0:amt])
}
action lookAheadWSP { p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t') }
action goto_name_addr {
p = mark
fhold;
fgoto name_addr;
}
action goto_bare_addr {
p = mark
fhold;
fgoto bare_addr;
}
action goto_params {
fhold;
fgoto params;
}
action goto_addr {
addr.Next = new(Addr)
addr = addr.Next
fhold;
fgoto addr;
}
# https://tools.ietf.org/html/rfc2234
SP = " ";
HTAB = "\t";
CR = "\r";
LF = "\n";
DQUOTE = "\"";
CRLF = CR LF;
WSP = SP | HTAB;
LWS = ( WSP* ( CR when lookAheadWSP ) LF )? WSP+;
SWS = LWS?;
UTF8_CONT = 0x80..0xBF @append;
UTF8_NONASCII = 0xC0..0xDF @append UTF8_CONT {1}
| 0xE0..0xEF @append UTF8_CONT {2}
| 0xF0..0xF7 @append UTF8_CONT {3}
| 0xF8..0xFb @append UTF8_CONT {4}
| 0xFC..0xFD @append UTF8_CONT {5};
UTF8 = 0x21..0x7F @append | UTF8_NONASCII;
LWSCRLF_append = ( CR when lookAheadWSP ) @append LF @append;
LWS_append = ( WSP* @append LWSCRLF_append )? WSP+ @append;
# https://tools.ietf.org/html/rfc3261#section-25.1
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," ;
mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" ;
unreserved = alnum | mark ;
tokenc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`"
| "'" | "~" ;
separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\"
| "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP
| HTAB ;
wordc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`"
| "'" | "~" | "(" | ")" | "<" | ">" | ":" | "\\" | "\""
| "/" | "[" | "]" | "?" | "{" | "}" ;
word = wordc+;
STAR = SWS "*" SWS;
SLASH = SWS "/" SWS;
EQUAL = SWS "=" SWS;
LPAREN = SWS "(" SWS;
RPAREN = SWS ")" SWS;
RAQUOT = ">" SWS;
LAQUOT = SWS "<";
COMMA = SWS "," SWS;
SEMI = SWS ";" SWS;
COLON = SWS ":" SWS;
HCOLON = WSP* ":" SWS;
LDQUOT = SWS "\"";
RDQUOT = "\"" SWS;
# The RFC BNF for SIP addresses is a bit nuts; we'll try our best.
token = tokenc+;
tokenhost = ( tokenc | "[" | "]" | ":" )+;
schemec = alnum | "+" | "-" | ".";
scheme = alpha schemec*;
uric = reserved | unreserved | "%" | "[" | "]";
uri = scheme ":" uric+;
# Quoted strings can have just about anything, including backslash escapes,
# which aren't quite as fancy as the ones you'd see in programming.
qdtextc = 0x21 | 0x23..0x5B | 0x5D..0x7E;
qdtext = UTF8_NONASCII | LWS_append | qdtextc @append;
quoted_pair = "\\" ( 0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F ) @append;
quoted_content = ( qdtext | quoted_pair )* >start;
quoted_string = SWS DQUOTE quoted_content DQUOTE;
# The display name can either be quoted or unquoted. The unquoted form is
# much more limited in what it may contain, although it does permit spaces.
display_name = quoted_string %qdisplay
| SWS ( token @append LWS_append )* >start %display
;
# Parameter parsing is pretty straightforward. Unlike URI parameters, the
# values on these can be quoted. The only thing that's weird is if the URI
# doesn't appear in angle brackets, the parameters need to be owned by the
# Addr object, not the URI. This has been placed in its own machine so it
# doesn't have to be duplicated in the two machines below.
param_name = token >mark %name;
param_content = tokenhost >start @append;
param_value = param_content | quoted_string;
param = ( param_name ( EQUAL param_value )? ) %param;
params := ( SEMI param )* ( COMMA <: any @goto_addr )?;
# Now we're going to define two separate machines. The first is for
# addresses with angle brackets and the second is for ones without.
addr_spec = uri >mark %uri;
name_addr := display_name? LAQUOT addr_spec RAQUOT <: ( any @goto_params )?;
bare_addr := ( addr_spec -- ";" ) <: ( any @goto_params )?;
# Now we perform lookahead to determine which machine we should use.
look = [^;<]
| ";" @goto_bare_addr
| "<" @goto_name_addr
;
addr := look+ >mark $eof(goto_name_addr);
write init;
write exec;
}%%
if cs < addr_first_final {
if p == pe {
return nil, errors.New(fmt.Sprintf("Incomplete SIP address: %s", data))
} else {
return nil, errors.New(fmt.Sprintf("Error in SIP address at offset %d: %s", p, string(data)))
}
}
if next != nil {
next.Last().Next = result
return next, nil
}
return result, nil
}

+ 4
- 0
sip/addr_test.go View File

@ -153,3 +153,7 @@ func TestReversed(t *testing.T) {
t.Error("wtf", b.Next.Next)
}
}
func addrError(t *testing.T, name string, want, got *sip.Addr) {
t.Errorf("%s:\n%#v\n%#v\n!=\n%#v\n%#v", name, want, want.Uri, got, got.Uri)
}

+ 4072
- 8603
sip/msg_parse.go
File diff suppressed because it is too large
View File


+ 66
- 87
sip/msg_parse.rl View File

@ -6,13 +6,12 @@ import (
"errors"
"fmt"
"github.com/jart/gosip/sdp"
"strings"
)
%% machine msg;
%% write data;
// ParseMsg turns a a SIP message into a data structure.
// ParseMsg turns a SIP message into a data structure.
func ParseMsg(s string) (msg *Msg, err error) {
if s == "" {
return nil, errors.New("Empty SIP message")
@ -20,20 +19,13 @@ func ParseMsg(s string) (msg *Msg, err error) {
return ParseMsgBytes([]byte(s))
}
// ParseMsg turns a a SIP message byte slice into a data structure.
// ParseMsg turns a SIP message byte slice into a data structure.
func ParseMsgBytes(data []byte) (msg *Msg, err error) {
if data == nil {
return nil, nil
}
msg = new(Msg)
fromp := &msg.From
top := &msg.To
viap := &msg.Via
routep := &msg.Route
rroutep := &msg.RecordRoute
contactp := &msg.Contact
paidp := &msg.PAssertedIdentity
rpidp := &msg.RemotePartyID
cs := 0
p := 0
pe := len(data)
@ -48,14 +40,18 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
var name string
var hex byte
var value *string
var addr *Addr
var addrpp ***Addr
var addr **Addr
%%{
action break {
fbreak;
}
action line {
line++
linep = p + 1
}
action mark {
mark = p
}
@ -74,10 +70,6 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
amt++
}
action erase {
amt--
}
action collapse {
amt = appendCollapse(buf, amt, fc)
}
@ -121,18 +113,13 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
msg.Phrase = string(buf[0:amt])
}
action goto_avalue {
fhold;
fgoto avalue;
}
action goto_header {
fgoto header;
}
action goto_svalue {
action goto_value {
fhold;
fgoto svalue;
fgoto value;
}
action goto_xheader {
@ -154,6 +141,9 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
b := data[mark:p - 1]
if value != nil {
*value = string(b)
} else if addr != nil {
*addr, err = ParseAddrBytes(b, *addr)
if err != nil { return nil, err }
} else {
if msg.Headers == nil {
msg.Headers = Headers{}
@ -162,15 +152,6 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
}
}}
action store_addr {
// TODO(jart): Why does this fire multiple times?
if addr != nil {
**addrpp = addr
*addrpp = &addr.Next
addr = nil
}
}
action new_addr {
addr = new(Addr)
}
@ -229,17 +210,18 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
for *viap != nil { viap = &(*viap).Next }
}
action lookAheadWSP { p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t') }
action lookAheadWSP { lookAheadWSP(data, p, pe) }
# https://tools.ietf.org/html/rfc2234
SP = " ";
HTAB = "\t";
CR = "\r";
LF = "\n" @{ line++; linep = p; };
LF = "\n" @line;
CRLF = CR LF;
WSP = SP | HTAB;
LWS = ( WSP* ( CR when lookAheadWSP ) LF )? WSP+;
SWS = LWS?;
UTF8_CONT = 0x80..0xBF @append;
UTF8_NONASCII = 0xC0..0xDF @append UTF8_CONT {1}
| 0xE0..0xEF @append UTF8_CONT {2}
@ -247,41 +229,48 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
| 0xF8..0xFb @append UTF8_CONT {4}
| 0xFC..0xFD @append UTF8_CONT {5};
UTF8 = 0x21..0x7F @append | UTF8_NONASCII;
UTF8_TRIM = ( UTF8+ (LWS* UTF8)* ) >start @collapse;
mUTF8_CONT = 0x80..0xBF;
mUTF8_NONASCII = 0xC0..0xDF mUTF8_CONT {1}
| 0xE0..0xEF mUTF8_CONT {2}
| 0xF0..0xF7 mUTF8_CONT {3}
| 0xF8..0xFb mUTF8_CONT {4}
| 0xFC..0xFD mUTF8_CONT {5};
mUTF8 = 0x21..0x7F | mUTF8_NONASCII;
# https://tools.ietf.org/html/rfc3261#section-25.1
reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," ;
mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" ;
unreserved = alpha | digit | mark ;
tokenc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
| "+" | "`" | "'" | "~" ;
unreserved = alnum | mark ;
tokenc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`"
| "'" | "~" ;
separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\"
| "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP
| HTAB ;
wordc = alpha | digit | "-" | "." | "!" | "%" | "*" | "_"
| "+" | "`" | "'" | "~" | "(" | ")" | "<" | ">" | ":"
| "\\" | "\"" | "/" | "[" | "]" | "?" | "{" | "}" ;
schmchars = alpha | digit | "+" | "-" | "." ;
word = wordc+ ;
STAR = SWS "*" SWS ;
SLASH = SWS "/" SWS ;
EQUAL = SWS "=" SWS ;
LPAREN = SWS "(" SWS ;
RPAREN = SWS ")" SWS ;
RAQUOT = ">" SWS ;
LAQUOT = SWS "<" ;
COMMA = SWS "," SWS ;
SEMI = SWS ";" SWS ;
COLON = SWS ":" SWS ;
HCOLON = WSP* ":" SWS ;
LDQUOT = SWS "\"" ;
RDQUOT = "\"" SWS ;
wordc = alnum | "-" | "." | "!" | "%" | "*" | "_" | "+" | "`"
| "'" | "~" | "(" | ")" | "<" | ">" | ":" | "\\" | "\""
| "/" | "[" | "]" | "?" | "{" | "}" ;
schmchars = alnum | "+" | "-" | "." ;
word = wordc+;
STAR = SWS "*" SWS;
SLASH = SWS "/" SWS;
EQUAL = SWS "=" SWS;
LPAREN = SWS "(" SWS;
RPAREN = SWS ")" SWS;
RAQUOT = ">" SWS;
LAQUOT = SWS "<";
COMMA = SWS "," SWS;
SEMI = SWS ";" SWS;
COLON = SWS ":" SWS;
HCOLON = WSP* ":" SWS;
LDQUOT = SWS "\"";
RDQUOT = "\"" SWS;
ctext = 0x21..0x27 | 0x2A..0x5B | 0x5D..0x7E | UTF8_NONASCII | LWS;
quoted_pair = "\\" ( 0x00..0x09 | 0x0B..0x0C | 0x0E..0x7F ) @append;
comment = LPAREN ( ctext | quoted_pair )* <: RPAREN; # TODO(jart): Nested parens.
qdtext = UTF8_NONASCII | LWS @append | ( 0x21 | 0x23..0x5B | 0x5D..0x7E ) @append;
escaped = "%" ( xdigit @hexHi ) ( xdigit @hexLo ) ;
uric = reserved | unreserved | "%" ;
uric = reserved | unreserved | "%" | "[" | "]";
uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" | "&" | "="
| "+" | "$" | "," ;
uri = alpha schmchars* ":" uric+;
@ -291,34 +280,21 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
reasonmc = escaped | reasonc;
cid = word ( "@" word )?;
quoted_content = ( qdtext | quoted_pair )* >start;
quoted_string = LDQUOT quoted_content <: RDQUOT;
param_name = token >mark %store_name;
param_content = tokenhost >start @append;
param_value = param_content | quoted_string;
param = ( param_name ( EQUAL param_value )? ) %addr_param;
display = ( token @append LWS %space )* >start;
display_name = ( display | quoted_string ) %addr_display;
addr_spec = uri >mark %addr_uri;
name_addr = display_name? LAQUOT addr_spec RAQUOT;
addr_omg = ( ( addr_spec -- ";" ) | name_addr ) ( SEMI param )*;
addr = addr_omg >new_addr %store_addr;
Method = token >mark %Method;
SIPVersionNo = digit+ @VersionMajor "." digit+ @VersionMinor;
RequestURI = ^SP+ >mark %RequestURI;
StatusCode = ( digit @StatusCode ) {3};
ReasonPhrase = reasonmc+ >start %ReasonPhrase;
hval = ( UTF8 | LWS )* >mark;
hval = ( mUTF8 | LWS )* >mark;
# Address Headers
aname = ("Contact"i | "m"i) %{addrpp=&contactp}
| ("From"i | "f"i) %{addrpp=&fromp}
| "P-Asserted-Identity"i %{addrpp=&paidp}
| "Record-Route"i %{addrpp=&rroutep}
| "Remote-Party-ID"i %{addrpp=&rpidp}
| "Route"i %{addrpp=&routep}
| ("To"i | "t"i) %{addrpp=&top}
aname = ("Contact"i | "m"i) %{addr=&msg.Contact}
| ("From"i | "f"i) %{addr=&msg.From}
| "P-Asserted-Identity"i %{addr=&msg.PAssertedIdentity}
| "Record-Route"i %{addr=&msg.RecordRoute}
| "Remote-Party-ID"i %{addr=&msg.RemotePartyID}
| "Route"i %{addr=&msg.Route}
| ("To"i | "t"i) %{addr=&msg.To}
;
# String Headers
@ -371,18 +347,17 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
| ("Via"i | "v"i) @!gxh HCOLON <: hval %Via
;
avalue := addr ( COMMA addr )* CR LF @goto_header;
svalue := hval CR LF @store_value @goto_header;
xheader := token >mark %store_name HCOLON <: any @{value=nil} @goto_svalue;
header := CR LF @break
| cheader CR LF @goto_header
| aname >mark @err(goto_xheader) HCOLON <: any @goto_avalue
| sname >mark @err(goto_xheader) HCOLON <: any @goto_svalue
value := hval <: CRLF @store_value @goto_header;
xheader := token >mark %store_name HCOLON <: any @{value=nil;addr=nil} @goto_value;
header := CRLF @break
| cheader <: CRLF @goto_header
| aname >mark @err(goto_xheader) HCOLON <: any @{value=nil} @goto_value
| sname >mark @err(goto_xheader) HCOLON <: any @{addr=nil} @goto_value
;
SIPVersion = "SIP/" SIPVersionNo;
Request = Method SP RequestURI SP SIPVersion CR LF @goto_header;
Response = SIPVersion SP StatusCode SP ReasonPhrase CR LF @goto_header;
Request = Method SP RequestURI SP SIPVersion CRLF @goto_header;
Response = SIPVersion SP StatusCode SP ReasonPhrase CRLF @goto_header;
main := Request | Response;
write init;
@ -412,3 +387,7 @@ func ParseMsgBytes(data []byte) (msg *Msg, err error) {
return msg, nil
}
func lookAheadWSP(data []byte, p, pe int) bool {
return p + 2 < pe && (data[p+2] == ' ' || data[p+2] == '\t')
}

+ 74
- 5
sip/msg_test.go View File

@ -79,7 +79,30 @@ var msgTests = []msgTest{
},
msgTest{
name: "Line Continuations",
name: "Line Continuation Warning",
s: "SIP/2.0 200 OK\r\n" +
"Warning: Morning and evening\r\n" +
" Maids heard the goblins cry:\r\n" +
" “Come buy our orchard fruits,\r\n" +
" Come buy, come buy:\r\n" +
" Apples and quinces,\r\n" +
" Lemons and oranges\r\n" +
"\r\n",
msg: sip.Msg{
VersionMajor: 2,
Status: 200,
Phrase: "OK",
Warning: "Morning and evening\r\n" +
" Maids heard the goblins cry:\r\n" +
" “Come buy our orchard fruits,\r\n" +
" Come buy, come buy:\r\n" +
" Apples and quinces,\r\n" +
" Lemons and oranges",
},
},
msgTest{
name: "Line Continuations Addr",
s: "SIP/2.0 200 OK\r\n" +
"From:\r\n" +
" <sip:lol.com>,\r\n" +
@ -142,6 +165,52 @@ var msgTests = []msgTest{
},
},
msgTest{
name: "Address Quoted Display Multiline",
s: "SIP/2.0 200 OK\r\n" +
"From: \"oh\r\n" +
" my \r\n" +
" goth\" <sip:lol.com>\r\n" +
"\r\n",
msg: sip.Msg{
VersionMajor: 2,
Status: 200,
Phrase: "OK",
From: &sip.Addr{
Display: "oh\r\n" +
" my \r\n" +
" goth",
Uri: &sip.URI{
Scheme: "sip",
Host: "lol.com",
},
},
},
},
msgTest{
name: "Address Unquoted Display Multiline",
s: "SIP/2.0 200 OK\r\n" +
"From: oh\r\n" +
" my \r\n" +
" goth <sip:lol.com>\r\n" +
"\r\n",
msg: sip.Msg{
VersionMajor: 2,
Status: 200,
Phrase: "OK",
From: &sip.Addr{
Display: "oh\r\n" +
" my \r\n" +
" goth",
Uri: &sip.URI{
Scheme: "sip",
Host: "lol.com",
},
},
},
},
msgTest{
name: "Addr Tag",
s: "SIP/2.0 200 OK\r\n" +
@ -594,16 +663,16 @@ func TestParseMsg(t *testing.T) {
t.Errorf("Request:\n%#v !=\n%#v", test.msg.Request, msg.Request)
}
if !reflect.DeepEqual(test.msg.To, msg.To) {
t.Errorf("To:\n%#v !=\n%#v", test.msg.To, msg.To)
addrError(t, "To", test.msg.To, msg.To)
}
if !reflect.DeepEqual(test.msg.From, msg.From) {
t.Errorf("From:\n%#v !=\n%#v", test.msg.From, msg.From)
addrError(t, "From", test.msg.From, msg.From)
}
if !reflect.DeepEqual(test.msg.Contact, msg.Contact) {
t.Errorf("Contact:\n%#v !=\n%#v", test.msg.Contact, msg.Contact)
addrError(t, "Contact", test.msg.Contact, msg.Contact)
}
if !reflect.DeepEqual(test.msg.RecordRoute, msg.RecordRoute) {
t.Errorf("RecordRoute:\n%#v !=\n%#v", test.msg.RecordRoute, msg.RecordRoute)
addrError(t, "RecordRoute", test.msg.RecordRoute, msg.RecordRoute)
}
}
}


Loading…
Cancel
Save