@@ -12,6 +12,8 @@ import org.operatorfoundation.codex.symbols.CallLetterSpace
1212import org.operatorfoundation.codex.symbols.GridLetter
1313import org.operatorfoundation.codex.symbols.Power
1414import org.operatorfoundation.codex.symbols.Trinary
15+ import org.operatorfoundation.codex.symbols.WSPRMessage
16+ import org.operatorfoundation.codex.symbols.WSPRMessageSequence
1517
1618import java.math.BigInteger
1719
@@ -174,5 +176,204 @@ class EncoderDecoderTest
174176 assertEquals(bigIntValue, decoded, " Round-trip failed for value $value " )
175177 }
176178 }
179+ }
180+
181+ @Test
182+ fun testWSPRMessageRoundTrip () {
183+ val wsprMessage = WSPRMessage ()
184+
185+ // Test various values within WSPR message capacity
186+ val testValues = listOf (
187+ BigInteger .ZERO ,
188+ BigInteger .ONE ,
189+ BigInteger .valueOf(1415934836 ), // "Test" as integer
190+ BigInteger .valueOf(123456789 ),
191+ BigInteger .valueOf(999999999 ),
192+ BigInteger (" 10000000000" ) // Larger value
193+ )
194+
195+ testValues.forEach { value ->
196+ val encoded = wsprMessage.encode(value)
197+
198+ // Verify size
199+ assertEquals(wsprMessage.size(), encoded.size,
200+ " Encoded size mismatch for value $value " )
201+
202+ // Decode and verify
203+ val decoded = wsprMessage.decode(encoded)
204+ assertEquals(value, decoded, " Round-trip failed for value $value " )
205+
206+ println (" WSPRMessage round-trip: $value -> ${encoded.size} bytes -> $decoded " )
207+ }
208+ }
209+
210+ @Test
211+ fun testWSPRMessageFormat () {
212+ val wsprMessage = WSPRMessage ()
213+ val testInteger = BigInteger .valueOf(1415934836 ) // "Test"
214+
215+ val encoded = wsprMessage.encode(testInteger)
216+
217+ // Verify the message starts with 'Q'
218+ assertEquals(' Q' .code.toByte(), encoded[0 ], " WSPR message should start with 'Q'" )
219+
220+ // Decode and display the message components
221+ val parts = mutableListOf<String >()
222+ parts.add(encoded[0 ].toInt().toChar().toString()) // Required 'Q'
223+ parts.add((1 .. 6 ).map { encoded[it].toInt().toChar() }.joinToString(" " )) // Callsign
224+ parts.add((7 .. 8 ).map { encoded[it].toInt().toChar() }.joinToString(" " )) // Grid letters
225+ parts.add((9 .. 10 ).map { encoded[it].toInt().toChar() }.joinToString(" " )) // Grid numbers
226+ parts.add(encoded[11 ].toInt().toChar().toString()) // Power
227+
228+ println (" WSPR Message components: ${parts.joinToString(" " )} " )
229+
230+ // Verify decoding
231+ val decoded = wsprMessage.decode(encoded)
232+ assertEquals(testInteger, decoded)
233+ }
234+
235+ @Test
236+ fun testWSPRMessageSequenceSingleMessage () {
237+ // Test with a sequence of 1 message (should behave like WSPRMessage)
238+ val sequence = WSPRMessageSequence (1 )
239+ val singleMessage = WSPRMessage ()
240+
241+ val testValue = BigInteger .valueOf(1415934836 )
242+
243+ val seqEncoded = sequence.encode(testValue)
244+ val msgEncoded = singleMessage.encode(testValue)
245+
246+ // Should produce identical results
247+ assertArrayEquals(msgEncoded, seqEncoded)
248+
249+ // Verify decoding
250+ val decoded = sequence.decode(seqEncoded)
251+ assertEquals(testValue, decoded)
252+ }
253+
254+ @Test
255+ fun testWSPRMessageSequenceMultipleMessages () {
256+ // Test with 3 messages
257+ val sequence = WSPRMessageSequence (3 )
258+
259+ val testValues = listOf (
260+ BigInteger .ZERO ,
261+ BigInteger .ONE ,
262+ BigInteger .valueOf(1415934836 ),
263+ BigInteger (" 100000000000000" ), // Large value requiring multiple messages
264+ BigInteger (" 999999999999999999" )
265+ )
266+
267+ testValues.forEach { value ->
268+ val encoded = sequence.encode(value)
269+
270+ // Verify total size is 3 × single message size
271+ val singleMessageSize = WSPRMessage ().size()
272+ assertEquals(singleMessageSize * 3 , encoded.size,
273+ " Sequence size should be 3 × single message size" )
274+
275+ // Decode and verify
276+ val decoded = sequence.decode(encoded)
277+ assertEquals(value, decoded, " Round-trip failed for value $value " )
278+
279+ println (" WSPRMessageSequence(3) round-trip: $value -> ${encoded.size} bytes -> $decoded " )
280+ }
281+ }
282+
283+ @Test
284+ fun testWSPRMessageSequenceCapacity () {
285+ // Test that larger sequences can handle larger numbers
286+ val sequence2 = WSPRMessageSequence (2 )
287+ val sequence5 = WSPRMessageSequence (5 )
288+
289+ // A very large number that would overflow a single message
290+ val largeValue = BigInteger (" 123456789012345678901234567890" )
291+
292+ try {
293+ val encoded2 = sequence2.encode(largeValue)
294+ val decoded2 = sequence2.decode(encoded2)
295+ assertEquals(largeValue, decoded2, " Sequence of 2 failed for large value" )
296+ println (" WSPRMessageSequence(2) handled large value successfully" )
297+ } catch (e: Exception ) {
298+ println (" WSPRMessageSequence(2) cannot handle value (expected if too large)" )
299+ }
177300
301+ try {
302+ val encoded5 = sequence5.encode(largeValue)
303+ val decoded5 = sequence5.decode(encoded5)
304+ assertEquals(largeValue, decoded5, " Sequence of 5 failed for large value" )
305+ println (" WSPRMessageSequence(5) handled large value: $largeValue " )
306+ } catch (e: Exception ) {
307+ println (" WSPRMessageSequence(5) cannot handle value (too large even for 5 messages)" )
308+ }
309+ }
310+
311+ @Test
312+ fun testWSPRMessageSequenceIndependence () {
313+ // Verify that each message in the sequence contributes independently
314+ val sequence = WSPRMessageSequence (2 )
315+ val singleMessageSize = WSPRMessage ().size()
316+
317+ val testValue = BigInteger .valueOf(1000000 )
318+ val encoded = sequence.encode(testValue)
319+
320+ // Split into two messages
321+ val message1 = encoded.sliceArray(0 until singleMessageSize)
322+ val message2 = encoded.sliceArray(singleMessageSize until encoded.size)
323+
324+ println (" Message 1 bytes: ${message1.map { (it.toInt() and 0xFF ).toString(16 ) }} " )
325+ println (" Message 2 bytes: ${message2.map { (it.toInt() and 0xFF ).toString(16 ) }} " )
326+
327+ // Both should start with 'Q'
328+ assertEquals(' Q' .code.toByte(), message1[0 ], " First message should start with Q" )
329+ assertEquals(' Q' .code.toByte(), message2[0 ], " Second message should start with Q" )
330+
331+ // Verify full round-trip still works
332+ val decoded = sequence.decode(encoded)
333+ assertEquals(testValue, decoded)
334+ }
335+
336+ @Test
337+ fun testWSPRMessageVsSequenceComparison () {
338+ // Compare the capacity and behavior of single vs multiple messages
339+ val single = WSPRMessage ()
340+ val double = WSPRMessageSequence (2 )
341+
342+ // Find a value that fits in single message
343+ val smallValue = BigInteger .valueOf(1000 )
344+ val singleEncoded = single.encode(smallValue)
345+ val doubleEncoded = double.encode(smallValue)
346+
347+ println (" Single message size: ${singleEncoded.size} bytes" )
348+ println (" Double sequence size: ${doubleEncoded.size} bytes" )
349+
350+ assertEquals(single.size(), singleEncoded.size)
351+ assertEquals(single.size() * 2 , doubleEncoded.size)
352+
353+ // Both should decode correctly
354+ assertEquals(smallValue, single.decode(singleEncoded))
355+ assertEquals(smallValue, double.decode(doubleEncoded))
356+ }
357+
358+ @Test
359+ fun testWSPRMessageSequenceEdgeCases () {
360+ // Test edge cases
361+ val sequence = WSPRMessageSequence (1 )
362+
363+ // Test zero
364+ val zeroEncoded = sequence.encode(BigInteger .ZERO )
365+ assertEquals(BigInteger .ZERO , sequence.decode(zeroEncoded))
366+
367+ // Test one
368+ val oneEncoded = sequence.encode(BigInteger .ONE )
369+ assertEquals(BigInteger .ONE , sequence.decode(oneEncoded))
370+
371+ // Test that invalid count throws
372+ assertThrows(IllegalArgumentException ::class .java) {
373+ WSPRMessageSequence (0 )
374+ }
375+
376+ assertThrows(IllegalArgumentException ::class .java) {
377+ WSPRMessageSequence (- 1 )
378+ }
178379}
0 commit comments