From a1deb2ffa3c5a4e9b1ae42ce1e2e4a2d2fe0cde9 Mon Sep 17 00:00:00 2001 From: Joerg Schultze-Lutter Date: Thu, 9 Sep 2021 22:13:33 +0200 Subject: [PATCH 1/6] ackMsgNo support (http://www.aprs.org/aprs11/replyacks.txt) Decode the 'new' ackMsgNo for ack/rej responses and standard APRS messages while still honoring the original format from aprs101.pdf The following assumptions apply when handling APRS messages in general: Option 1: no message ID present: send no ACK outgoing messages have no msg number attachment Example data exchange 1: DF1JSL-4>APRS,TCPIP*,qAC,T2PRT::WXBOT :94043 WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Mountain View CA. Today,Sunny High 60 Example data exchange 2: DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :blah@gmail.com Hallo EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to blah@gmail.com Option 2: old message number format is present: (example: msg{12345) Send ack with message number from original message (ack12345) All outgoing messages have trailing msg number ( {abcde ); can be numeric or slphanumeric counter. See aprs101.pdf chapter 14 Example data exchange 1: DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :blah@gmail.com Hallo{12345 EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ack12345 EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to blah@gmail.com{891 DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack891 Example data exchange 2: DF1JSL-4>APRS,TCPIP*,qAC,T2CSNGRAD::EMAIL-2 :blah@gmail.com{ABCDE EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ackABCDE EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to blah@gmail.com{893 DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack893 Option 3: new messages with message ID but without trailing retry msg ids: msg{AB} Do NOT send extra ack All outgoing messages have 2-character msg id, followed by message ID from original message Example: User sends message "Hello{AB}" to MPAD MPAD responds "Message content line 1{DE}AB" to user MPAD responds "Message content line 2{DF}AB" to user AB -> original message DE, DF -> message IDs generated by MPAD Example data exchange 1: DF1JSL-4>APRS,TCPIP*,qAC,T2NUERNBG::WXBOT :99801{AB} WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% {QL}AB DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQL}AB WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :High 40{QM}AB DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQM}AB Example data exchange 2: DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :blah@gmail.com Hallo{AB} EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to blah@gmail.com{OQ}AB DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOQ}AB Option 4: new messages with message ID and with trailing retry msg ids: msg{AB}CD Follow the instructions as per http://www.aprs.org/aprs11/replyacks.txt Example data exchange 1: DF1JSL-4>APRS,TCPIP*,qAC,T2CZECH::WXBOT :99801{LM}AA WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% {QP}LM DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQP}LM WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :High 40{QQ}LM DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQQ}LM Example data exchange 2: DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :blah@gmail.com Welt{DE}FG EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to blah@gmail.com{OS}DE DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOS}DE --- aprslib/parsing/message.py | 88 +++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/aprslib/parsing/message.py b/aprslib/parsing/message.py index 3995629..e06ef88 100644 --- a/aprslib/parsing/message.py +++ b/aprslib/parsing/message.py @@ -72,25 +72,99 @@ def parse_message(body): else: logger.debug("Packet is just a regular message") parsed.update({'format': 'message'}) + has_matched = False + # APRS supports two different message formats: + # - the standard format which is described in 'aprs101.pdf': + # http://www.aprs.org/doc/APRS101.PDF + # - an addendum from 1999 which introduces a new format: + # http://www.aprs.org/aprs11/replyacks.txt + # + # A message (ack/rej as well as a standard msg text body) can either have: + # - no message number at all + # - a message number in the old format (1..5 characters / digits) + # - a message number in the new format (2 characters / digits) without trailing 'ack msg no' + # - a message number in the new format with trailing 'free ack msg no' (2 characters / digits) + + # + # ack / rej parser + # + # search for: ack/rej new format with msgNo present and ackMsgNo present + if not has_matched: + match = re.search(r"^(ack|rej)([A-Za-z0-9]{2})}([A-Za-z0-9]{2})$", body) + if match: + parsed.update({ + 'response': match.group(1), + 'msgNo': match.group(2), + 'ackMsgNo': match.group(3) + }) + if "message_text" in parsed: + parsed.pop("message_text") + has_matched = True + + # search for: ack/rej new format with msgNo present and ackMsgNo not present + if not has_matched: + match = re.search(r"^(ack|rej)([A-Za-z0-9]{2})}$", body) + if match: + parsed.update({ + 'response': match.group(1), + 'msgNo': match.group(2), + }) + if "message_text" in parsed: + parsed.pop("message_text") + has_matched = True + + # search for: ack/rej standard format as per aprs101.pdf chapter 14 match = re.search(r"^(ack|rej)([A-Za-z0-9]{1,5})$", body) if match: parsed.update({ 'response': match.group(1), 'msgNo': match.group(2), }) - else: - body = body[0:70] + if "message_text" in parsed: + parsed.pop("message_text") + has_matched = True - match = re.search(r"{([A-Za-z0-9]{1,5})$", body) + # + # regular message body parser + # + if not has_matched: + # new message format: http://www.aprs.org/aprs11/replyacks.txt + # search for: msgText/msgNo/ackMsgNo all present + match = re.search(r"{([A-Za-z0-9]{2})}([A-Za-z0-9]{2})$", body) if match: msgNo = match.group(1) - body = body[:len(body) - 1 - len(msgNo)] - + ackMsgNo = match.group(2) + body = body[:len(body) - 3 - len(msgNo) - len(ackMsgNo)] parsed.update({'msgNo': msgNo}) + parsed.update({'ackMsgNo': ackMsgNo}) + has_matched = True + parsed.update({'message_text': body[0:67].strip()}) - parsed.update({'message_text': body.strip(' ')}) + # new message format: http://www.aprs.org/aprs11/replyacks.txt + # search for: msgText/msgNo present and ackMsgNo not present + if not has_matched: + match = re.search(r"{([A-Za-z0-9]{2})}$", body) + if match: + msgNo = match.group(1) + body = body[:len(body) - 2 - len(msgNo)] + parsed.update({'msgNo': msgNo}) + has_matched = True + body = body[0:67].strip() + parsed.update({'message_text': body[0:67].strip()}) - break + # old message format - see aprs101.pdf. + # search for: msgNo present + if not has_matched: + match = re.search(r"{([A-Za-z0-9]{1,5})$", body) + if match: + msgNo = match.group(1) + body = body[:len(body) - 1 - len(msgNo)] + parsed.update({'msgNo': msgNo}) + has_matched = True + parsed.update({'message_text': body[0:67].strip()}) + + # break free from the eternal 'while' + break return ('', parsed) From bee37df76d3896dbf18f8cc564aedf2ccaf8c382 Mon Sep 17 00:00:00 2001 From: Joerg Schultze-Lutter Date: Thu, 9 Sep 2021 22:41:09 +0200 Subject: [PATCH 2/6] Fixed final msg parser bug when no msgno was present --- aprslib/parsing/message.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/aprslib/parsing/message.py b/aprslib/parsing/message.py index e06ef88..4e016f8 100644 --- a/aprslib/parsing/message.py +++ b/aprslib/parsing/message.py @@ -125,10 +125,17 @@ def parse_message(body): parsed.pop("message_text") has_matched = True + # escape from the eternal 'while' + if has_matched: + break + # # regular message body parser # if not has_matched: + # one-time truncate to 67 chars + body = body[0:67].strip() + # new message format: http://www.aprs.org/aprs11/replyacks.txt # search for: msgText/msgNo/ackMsgNo all present match = re.search(r"{([A-Za-z0-9]{2})}([A-Za-z0-9]{2})$", body) @@ -139,7 +146,6 @@ def parse_message(body): parsed.update({'msgNo': msgNo}) parsed.update({'ackMsgNo': ackMsgNo}) has_matched = True - parsed.update({'message_text': body[0:67].strip()}) # new message format: http://www.aprs.org/aprs11/replyacks.txt # search for: msgText/msgNo present and ackMsgNo not present @@ -150,8 +156,6 @@ def parse_message(body): body = body[:len(body) - 2 - len(msgNo)] parsed.update({'msgNo': msgNo}) has_matched = True - body = body[0:67].strip() - parsed.update({'message_text': body[0:67].strip()}) # old message format - see aprs101.pdf. # search for: msgNo present @@ -162,7 +166,9 @@ def parse_message(body): body = body[:len(body) - 1 - len(msgNo)] parsed.update({'msgNo': msgNo}) has_matched = True - parsed.update({'message_text': body[0:67].strip()}) + + # update the potentially changed message body in our dictionary + parsed.update({'message_text': body[0:67].strip()}) # break free from the eternal 'while' break From 90f05c9a4a8235f8dc0b7e05fe6e049677403477 Mon Sep 17 00:00:00 2001 From: Joerg Schultze-Lutter Date: Thu, 9 Sep 2021 23:08:24 +0200 Subject: [PATCH 3/6] Added test script for new APRS 'message' format field 'ackMsgNo' --- tests/test_old_new_replyack_format.py | 176 ++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 tests/test_old_new_replyack_format.py diff --git a/tests/test_old_new_replyack_format.py b/tests/test_old_new_replyack_format.py new file mode 100644 index 0000000..0a4378f --- /dev/null +++ b/tests/test_old_new_replyack_format.py @@ -0,0 +1,176 @@ +import aprslib +import logging + +logging.basicConfig( + level=logging.DEBUG, format="%(asctime)s %(module)s -%(levelname)s- %(message)s" +) +logger = logging.getLogger(__name__) + +""" + Have a look at the incoming APRS message and check if it + contains a message no which does not follow the APRS + standard (see aprs101.pdf chapter 14) + but rather follow the new format + http://www.aprs.org/aprs11/replyacks.txt + +The following assumptions apply when handling APRS messages in general: + +Option 1: no message ID present: + send no ACK + outgoing messages have no msg number attachment + Example data exchange 1: + DF1JSL-4>APRS,TCPIP*,qAC,T2PRT::WXBOT :94043 + WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Mountain View CA. Today,Sunny High 60 + + Example data exchange 2: + DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :jsl24469@gmail.com Hallo + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com + +Option 2: old message number format is present: (example: msg{12345) + Send ack with message number from original message (ack12345) + All outgoing messages have trailing msg number ( {abcde ); can be numeric or + slphanumeric counter. See aprs101.pdf chapter 14 + Example data exchange 1: + DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :jsl24469@gmail.com Hallo{12345 + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ack12345 + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{891 + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack891 + + Example data exchange 2: + DF1JSL-4>APRS,TCPIP*,qAC,T2CSNGRAD::EMAIL-2 :jsl24469@gmail.com{ABCDE + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ackABCDE + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{893 + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack893 + +Option 3: new messages with message ID but without trailing retry msg ids: msg{AB} + Do NOT send extra ack + All outgoing messages have 2-character msg id, followed by message ID from original message + Example: + User sends message "Hello{AB}" to MPAD + MPAD responds "Message content line 1{DE}AB" to user + MPAD responds "Message content line 2{DF}AB" to user + + AB -> original message + DE, DF -> message IDs generated by MPAD + + Example data exchange 1: + DF1JSL-4>APRS,TCPIP*,qAC,T2NUERNBG::WXBOT :99801{AB} + WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% High 4{QL}AB + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQL}AB + WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :0{QM}AB + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQM}AB + + Example data exchange 2: + DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :jsl24469@gmail.com Hallo{AB} + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{OQ}AB + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOQ}AB + +Option 4: new messages with message ID and with trailing retry msg ids: msg{AB}CD + We don't handle retries - therefore, apply option #3 for processing these + the "CD" part gets omitted and is not used + + Example data exchange 1: + DF1JSL-4>APRS,TCPIP*,qAC,T2CZECH::WXBOT :99801{LM}AA + WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% High 4{QP}LM + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQP}LM + WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :0{QQ}LM + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQQ}LM + Example data exchange 2: + DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :jsl24469@gmail.com Welt{DE}FG + EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{OS}DE + DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOS}DE +""" + + + +if __name__ == "__main__": + + # ack/rej assertion tests + + # erroneous rej + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :red12345") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "red12345") + + # reject with "old" msgno + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :rej123") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "rej") + assert (packet["msgNo"] == "123") + + # ack with new msgNo but no ackMsgNo + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "ack") + assert (packet["msgNo"] == "AB") + + # ack with new msgNo and ackMsgNo + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}CD") + assert ("msgNo" in packet) + assert ("ackMsgNo" in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "ack") + assert (packet["msgNo"] == "AB") + assert (packet["ackMsgNo"] == "CD") + + # message text body tests + + # message body without msg no + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld ") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "HelloWorld") + + # message body with msg no - old format + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {ABCDE") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "ABCDE") + + # message body with msgNo (new format) and ackMsgNo missing + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "AB") + + # message body with msgNo and ackMsgNo (new format) + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}CD") + assert ("msgNo" in packet) + assert ("ackMsgNo" in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "AB") + assert (packet["ackMsgNo"] == "CD") + + # message body with really long message + packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "0000000000111111111122222222223333333333444444444455555555556666666") From 171ce425b4cb0d517351883c72bd449659b6979e Mon Sep 17 00:00:00 2001 From: Joerg Schultze-Lutter Date: Thu, 9 Sep 2021 23:13:24 +0200 Subject: [PATCH 4/6] Removed unnecessary comments --- tests/test_old_new_replyack_format.py | 76 --------------------------- 1 file changed, 76 deletions(-) diff --git a/tests/test_old_new_replyack_format.py b/tests/test_old_new_replyack_format.py index 0a4378f..4b17364 100644 --- a/tests/test_old_new_replyack_format.py +++ b/tests/test_old_new_replyack_format.py @@ -6,82 +6,6 @@ logging.basicConfig( ) logger = logging.getLogger(__name__) -""" - Have a look at the incoming APRS message and check if it - contains a message no which does not follow the APRS - standard (see aprs101.pdf chapter 14) - but rather follow the new format - http://www.aprs.org/aprs11/replyacks.txt - -The following assumptions apply when handling APRS messages in general: - -Option 1: no message ID present: - send no ACK - outgoing messages have no msg number attachment - Example data exchange 1: - DF1JSL-4>APRS,TCPIP*,qAC,T2PRT::WXBOT :94043 - WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Mountain View CA. Today,Sunny High 60 - - Example data exchange 2: - DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :jsl24469@gmail.com Hallo - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com - -Option 2: old message number format is present: (example: msg{12345) - Send ack with message number from original message (ack12345) - All outgoing messages have trailing msg number ( {abcde ); can be numeric or - slphanumeric counter. See aprs101.pdf chapter 14 - Example data exchange 1: - DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :jsl24469@gmail.com Hallo{12345 - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ack12345 - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{891 - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack891 - - Example data exchange 2: - DF1JSL-4>APRS,TCPIP*,qAC,T2CSNGRAD::EMAIL-2 :jsl24469@gmail.com{ABCDE - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :ackABCDE - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{893 - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ack893 - -Option 3: new messages with message ID but without trailing retry msg ids: msg{AB} - Do NOT send extra ack - All outgoing messages have 2-character msg id, followed by message ID from original message - Example: - User sends message "Hello{AB}" to MPAD - MPAD responds "Message content line 1{DE}AB" to user - MPAD responds "Message content line 2{DF}AB" to user - - AB -> original message - DE, DF -> message IDs generated by MPAD - - Example data exchange 1: - DF1JSL-4>APRS,TCPIP*,qAC,T2NUERNBG::WXBOT :99801{AB} - WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% High 4{QL}AB - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQL}AB - WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :0{QM}AB - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQM}AB - - Example data exchange 2: - DF1JSL-4>APRS,TCPIP*,qAC,T2SPAIN::EMAIL-2 :jsl24469@gmail.com Hallo{AB} - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{OQ}AB - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOQ}AB - -Option 4: new messages with message ID and with trailing retry msg ids: msg{AB}CD - We don't handle retries - therefore, apply option #3 for processing these - the "CD" part gets omitted and is not used - - Example data exchange 1: - DF1JSL-4>APRS,TCPIP*,qAC,T2CZECH::WXBOT :99801{LM}AA - WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :Lemon Creek AK. Today,Scattered Rain/Snow and Patchy Fog 50% High 4{QP}LM - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQP}LM - WXBOT>APRS,qAS,KI6WJP::DF1JSL-4 :0{QQ}LM - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackQQ}LM - Example data exchange 2: - DF1JSL-4>APRS,TCPIP*,qAC,T2SP::EMAIL-2 :jsl24469@gmail.com Welt{DE}FG - EMAIL-2>APJIE4,TCPIP*,qAC,AE5PL-JF::DF1JSL-4 :Email sent to jsl24469@gmail.com{OS}DE - DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::EMAIL-2 :ackOS}DE -""" - - if __name__ == "__main__": From 8928424a9c41bbbacece50eb623e7864feb62936 Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 27 Nov 2021 16:25:01 +0000 Subject: [PATCH 5/6] convert tests to use unittest --- tests/test_old_new_replyack_format.py | 174 ++++++++++++++------------ 1 file changed, 93 insertions(+), 81 deletions(-) diff --git a/tests/test_old_new_replyack_format.py b/tests/test_old_new_replyack_format.py index 4b17364..2662591 100644 --- a/tests/test_old_new_replyack_format.py +++ b/tests/test_old_new_replyack_format.py @@ -1,100 +1,112 @@ -import aprslib -import logging +import unittest +from aprslib.parsing.message import parse_message -logging.basicConfig( - level=logging.DEBUG, format="%(asctime)s %(module)s -%(levelname)s- %(message)s" -) -logger = logging.getLogger(__name__) +# ack/rej assertion tests +class AckRejTests(unittest.TestCase): - -if __name__ == "__main__": - - # ack/rej assertion tests - - # erroneous rej - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :red12345") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "red12345") + # errorneus rej + def test_errorneus_rej(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :red12345") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "red12345") # reject with "old" msgno - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :rej123") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "rej") - assert (packet["msgNo"] == "123") + def test_reject_old_msgno(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :rej123") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "rej") + assert (packet["msgNo"] == "123") # ack with new msgNo but no ackMsgNo - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "ack") - assert (packet["msgNo"] == "AB") + def test_ack_new_msgno_but_no_ack_msgno(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "ack") + assert (packet["msgNo"] == "AB") # ack with new msgNo and ackMsgNo - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}CD") - assert ("msgNo" in packet) - assert ("ackMsgNo" in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "ack") - assert (packet["msgNo"] == "AB") - assert (packet["ackMsgNo"] == "CD") + def test_ack_new_msgno_and_ackmsgno(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}CD") + assert ("msgNo" in packet) + assert ("ackMsgNo" in packet) + assert ("response" in packet) + assert (packet["format"] == "message") + assert ("message_text" not in packet) + assert (packet["response"] == "ack") + assert (packet["msgNo"] == "AB") + assert (packet["ackMsgNo"] == "CD") - # message text body tests +# message text body tests +class MessageTestBodyTests(unittest.TestCase): # message body without msg no - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld ") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "HelloWorld") + def test_message_without_msgno(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld ") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "HelloWorld") # message body with msg no - old format - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {ABCDE") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "ABCDE") + def test_message_body_with_no_msgno_oldformat(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {ABCDE") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "ABCDE") # message body with msgNo (new format) and ackMsgNo missing - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "AB") + def test_message_body_with_msgno_and_ackmsgno_missing_newformat(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}") + assert ("msgNo" in packet) + assert ("ackMsgNo" not in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "AB") # message body with msgNo and ackMsgNo (new format) - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}CD") - assert ("msgNo" in packet) - assert ("ackMsgNo" in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "AB") - assert (packet["ackMsgNo"] == "CD") + def test_message_body_with_msgno_and_ackmsgno_newformat(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}CD") + assert ("msgNo" in packet) + assert ("ackMsgNo" in packet) + assert ("response" not in packet) + assert (packet["format"] == "message") + assert ("message_text" in packet) + assert (packet["message_text"] == "HelloWorld") + assert (packet["msgNo"] == "AB") + assert (packet["ackMsgNo"] == "CD") # message body with really long message - packet = aprslib.parse("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "0000000000111111111122222222223333333333444444444455555555556666666") + def test_message_body_with_long_message(self): + with self.assertRaises(AssertionError): + packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777") + assert ("msgNo" not in packet) + assert ("ackMsgNo" not in packet) + assert ("message_text" in packet) + assert (packet["format"] == "message") + assert (packet["message_text"] == "0000000000111111111122222222223333333333444444444455555555556666666") From 303fe826bdb5d3ef51ecee5a811ca877c3ae8c6c Mon Sep 17 00:00:00 2001 From: Rossen Georgiev Date: Sat, 27 Nov 2021 19:31:07 +0000 Subject: [PATCH 6/6] parse: rework message rejack parsing + update tests --- aprslib/parsing/message.py | 158 ++++++++++---------------- tests/test_old_new_replyack_format.py | 112 ------------------ tests/test_parse_message_rejack.py | 124 ++++++++++++++++++++ 3 files changed, 182 insertions(+), 212 deletions(-) delete mode 100644 tests/test_old_new_replyack_format.py create mode 100644 tests/test_parse_message_rejack.py diff --git a/aprslib/parsing/message.py b/aprslib/parsing/message.py index 4e016f8..059913b 100644 --- a/aprslib/parsing/message.py +++ b/aprslib/parsing/message.py @@ -69,108 +69,66 @@ def parse_message(body): break # regular message - else: - logger.debug("Packet is just a regular message") - parsed.update({'format': 'message'}) - has_matched = False + # --------------------------- + logger.debug("Packet is just a regular message") + parsed.update({'format': 'message'}) - # APRS supports two different message formats: - # - the standard format which is described in 'aprs101.pdf': - # http://www.aprs.org/doc/APRS101.PDF - # - an addendum from 1999 which introduces a new format: - # http://www.aprs.org/aprs11/replyacks.txt - # - # A message (ack/rej as well as a standard msg text body) can either have: - # - no message number at all - # - a message number in the old format (1..5 characters / digits) - # - a message number in the new format (2 characters / digits) without trailing 'ack msg no' - # - a message number in the new format with trailing 'free ack msg no' (2 characters / digits) + # APRS supports two different message formats: + # - the standard format which is described in 'aprs101.pdf': + # http://www.aprs.org/doc/APRS101.PDF + # - an addendum from 1999 which introduces a new format: + # http://www.aprs.org/aprs11/replyacks.txt + # + # A message (ack/rej as well as a standard msg text body) can either have: + # - no message number at all + # - a message number in the old format (1..5 characters / digits) + # - a message number in the new format (2 characters / digits) without trailing 'ack msg no' + # - a message number in the new format with trailing 'free ack msg no' (2 characters / digits) - # - # ack / rej parser - # - # search for: ack/rej new format with msgNo present and ackMsgNo present - if not has_matched: - match = re.search(r"^(ack|rej)([A-Za-z0-9]{2})}([A-Za-z0-9]{2})$", body) - if match: - parsed.update({ - 'response': match.group(1), - 'msgNo': match.group(2), - 'ackMsgNo': match.group(3) - }) - if "message_text" in parsed: - parsed.pop("message_text") - has_matched = True - - # search for: ack/rej new format with msgNo present and ackMsgNo not present - if not has_matched: - match = re.search(r"^(ack|rej)([A-Za-z0-9]{2})}$", body) - if match: - parsed.update({ - 'response': match.group(1), - 'msgNo': match.group(2), - }) - if "message_text" in parsed: - parsed.pop("message_text") - has_matched = True - - # search for: ack/rej standard format as per aprs101.pdf chapter 14 - match = re.search(r"^(ack|rej)([A-Za-z0-9]{1,5})$", body) - if match: - parsed.update({ - 'response': match.group(1), - 'msgNo': match.group(2), - }) - if "message_text" in parsed: - parsed.pop("message_text") - has_matched = True - - # escape from the eternal 'while' - if has_matched: - break - - # - # regular message body parser - # - if not has_matched: - # one-time truncate to 67 chars - body = body[0:67].strip() - - # new message format: http://www.aprs.org/aprs11/replyacks.txt - # search for: msgText/msgNo/ackMsgNo all present - match = re.search(r"{([A-Za-z0-9]{2})}([A-Za-z0-9]{2})$", body) - if match: - msgNo = match.group(1) - ackMsgNo = match.group(2) - body = body[:len(body) - 3 - len(msgNo) - len(ackMsgNo)] - parsed.update({'msgNo': msgNo}) - parsed.update({'ackMsgNo': ackMsgNo}) - has_matched = True - - # new message format: http://www.aprs.org/aprs11/replyacks.txt - # search for: msgText/msgNo present and ackMsgNo not present - if not has_matched: - match = re.search(r"{([A-Za-z0-9]{2})}$", body) - if match: - msgNo = match.group(1) - body = body[:len(body) - 2 - len(msgNo)] - parsed.update({'msgNo': msgNo}) - has_matched = True - - # old message format - see aprs101.pdf. - # search for: msgNo present - if not has_matched: - match = re.search(r"{([A-Za-z0-9]{1,5})$", body) - if match: - msgNo = match.group(1) - body = body[:len(body) - 1 - len(msgNo)] - parsed.update({'msgNo': msgNo}) - has_matched = True - - # update the potentially changed message body in our dictionary - parsed.update({'message_text': body[0:67].strip()}) - - # break free from the eternal 'while' + # ack / rej + # --------------------------- + # NEW REPLAY-ACK + # format: :AAAABBBBC:ackMM}AA + match = re.findall(r"^(ack|rej)([A-Za-z0-9]{2})}([A-Za-z0-9]{2})?$", body) + if match: + parsed['response'], parsed['msgNo'], ackMsgNo = match[0] + if ackMsgNo: + parsed['ackMsgNo'] = ackMsgNo break + # ack/rej standard format as per aprs101.pdf chapter 14 + # format: :AAAABBBBC:ack12345 + match = re.findall(r"^(ack|rej)([A-Za-z0-9]{1,5})$", body) + if match: + parsed['response'], parsed['msgNo'] = match[0] + break + + # regular message body parser + # --------------------------- + parsed['message_text'] = body.strip(' ') + + # check for ACKs + # new message format: http://www.aprs.org/aprs11/replyacks.txt + # format: :AAAABBBBC:text.....{MM}AA + match = re.findall(r"{([A-Za-z0-9]{2})}([A-Za-z0-9]{2})?$", body) + if match: + msgNo, ackMsgNo = match[0] + parsed['message_text'] = body[:len(body) - 4 - len(ackMsgNo)].strip(' ') + parsed['msgNo'] = msgNo + if ackMsgNo: + parsed['ackMsgNo'] = ackMsgNo + break + + # old message format - see aprs101.pdf. + # search for: msgNo present + match = re.findall(r"{([A-Za-z0-9]{1,5})$", body) + if match: + msgNo = match[0] + parsed['message_text'] = body[:len(body) - 1 - len(msgNo)].strip(' ') + parsed['msgNo'] = msgNo + break + + # break free from the eternal 'while' + break + return ('', parsed) diff --git a/tests/test_old_new_replyack_format.py b/tests/test_old_new_replyack_format.py deleted file mode 100644 index 2662591..0000000 --- a/tests/test_old_new_replyack_format.py +++ /dev/null @@ -1,112 +0,0 @@ -import unittest -from aprslib.parsing.message import parse_message - -# ack/rej assertion tests -class AckRejTests(unittest.TestCase): - - # errorneus rej - def test_errorneus_rej(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :red12345") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "red12345") - - # reject with "old" msgno - def test_reject_old_msgno(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :rej123") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "rej") - assert (packet["msgNo"] == "123") - - # ack with new msgNo but no ackMsgNo - def test_ack_new_msgno_but_no_ack_msgno(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "ack") - assert (packet["msgNo"] == "AB") - - # ack with new msgNo and ackMsgNo - def test_ack_new_msgno_and_ackmsgno(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :ackAB}CD") - assert ("msgNo" in packet) - assert ("ackMsgNo" in packet) - assert ("response" in packet) - assert (packet["format"] == "message") - assert ("message_text" not in packet) - assert (packet["response"] == "ack") - assert (packet["msgNo"] == "AB") - assert (packet["ackMsgNo"] == "CD") - -# message text body tests -class MessageTestBodyTests(unittest.TestCase): - - # message body without msg no - def test_message_without_msgno(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld ") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "HelloWorld") - - # message body with msg no - old format - def test_message_body_with_no_msgno_oldformat(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {ABCDE") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "ABCDE") - - # message body with msgNo (new format) and ackMsgNo missing - def test_message_body_with_msgno_and_ackmsgno_missing_newformat(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}") - assert ("msgNo" in packet) - assert ("ackMsgNo" not in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "AB") - - # message body with msgNo and ackMsgNo (new format) - def test_message_body_with_msgno_and_ackmsgno_newformat(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :HelloWorld {AB}CD") - assert ("msgNo" in packet) - assert ("ackMsgNo" in packet) - assert ("response" not in packet) - assert (packet["format"] == "message") - assert ("message_text" in packet) - assert (packet["message_text"] == "HelloWorld") - assert (packet["msgNo"] == "AB") - assert (packet["ackMsgNo"] == "CD") - - # message body with really long message - def test_message_body_with_long_message(self): - with self.assertRaises(AssertionError): - packet = parse_message("DF1JSL-4>APOSB,TCPIP*,qAS,DF1JSL::WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777") - assert ("msgNo" not in packet) - assert ("ackMsgNo" not in packet) - assert ("message_text" in packet) - assert (packet["format"] == "message") - assert (packet["message_text"] == "0000000000111111111122222222223333333333444444444455555555556666666") diff --git a/tests/test_parse_message_rejack.py b/tests/test_parse_message_rejack.py new file mode 100644 index 0000000..9fa4ea0 --- /dev/null +++ b/tests/test_parse_message_rejack.py @@ -0,0 +1,124 @@ +import unittest +from aprslib.parsing.message import parse_message + +# ack/rej assertion tests +class AckRejTests(unittest.TestCase): + + # errorneus rej + def test_errorneus_rej(self): + unparsed, result = parse_message("WXBOT :red12345") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': 'red12345', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # reject with "old" msgno + def test_reject_old_msgno(self): + unparsed, result = parse_message("WXBOT :rej123") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'msgNo': '123', + 'response': 'rej' + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # ack with new msgNo but no ackMsgNo + def test_ack_new_msgno_but_no_ack_msgno(self): + unparsed, result = parse_message("WXBOT :ackAB}") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'response': 'ack', + 'msgNo': 'AB', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # ack with new msgNo and ackMsgNo + def test_ack_new_msgno_and_ackmsgno(self): + unparsed, result = parse_message("WXBOT :ackAB}CD") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'response': 'ack', + 'msgNo': 'AB', + 'ackMsgNo': 'CD', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + +# message text body tests +class MessageTestBodyTests(unittest.TestCase): + + # message body without msg no + def test_message_without_msgno(self): + unparsed, result = parse_message("WXBOT :HelloWorld ") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': 'HelloWorld', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # message body with msg no - old format + def test_message_body_with_no_msgno_oldformat(self): + unparsed, result = parse_message("WXBOT :HelloWorld {ABCDE") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': 'HelloWorld', + 'msgNo': 'ABCDE', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # message body with msgNo (new format) and ackMsgNo missing + def test_message_body_with_msgno_and_ackmsgno_missing_newformat(self): + unparsed, result = parse_message("WXBOT :HelloWorld {AB}") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': 'HelloWorld', + 'msgNo': 'AB', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # message body with msgNo and ackMsgNo (new format) + def test_message_body_with_msgno_and_ackmsgno_newformat(self): + unparsed, result = parse_message("WXBOT :HelloWorld {AB}CD") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': 'HelloWorld', + 'msgNo': 'AB', + 'ackMsgNo': 'CD', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result) + + # message body with really long message + def test_message_body_with_long_message(self): + unparsed, result = parse_message("WXBOT :00000000001111111111222222222233333333334444444444555555555566666666667777777777") + expected = { + 'format': 'message', + 'addresse': 'WXBOT', + 'message_text': '00000000001111111111222222222233333333334444444444555555555566666666667777777777', + } + + self.assertEqual(unparsed, '') + self.assertEqual(expected, result)