diff --git a/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java b/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java index 5c39f8e..5a1df85 100644 --- a/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java +++ b/src/main/java/com/sipgate/li/simulator/controller/X2X3Controller.java @@ -1,5 +1,6 @@ package com.sipgate.li.simulator.controller; +import com.fasterxml.jackson.databind.ObjectMapper; import com.sipgate.li.lib.x2x3.protocol.PayloadDirection; import com.sipgate.li.lib.x2x3.protocol.PayloadFormat; import com.sipgate.li.lib.x2x3.protocol.PduObject; @@ -7,19 +8,31 @@ import com.sipgate.li.simulator.rtp.RtpMediaExtractor; import com.sipgate.li.simulator.x2x3.X2X3Memory; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; -import java.util.*; +import java.util.Base64; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.UUID; import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("/x2x3") @@ -66,6 +79,15 @@ public ResponseEntity getLast() throws IOException { } @Operation(summary = "Get all items from the X2X3 Storage") + @Parameters( + value = { + @Parameter( + name = "format", + schema = @Schema(allowableValues = { "java", "json", "hex", "base64", "" }, defaultValue = "base64"), + description = "The format of each element in the response" + ), + } + ) @ApiResponses( value = { @ApiResponse( @@ -75,12 +97,21 @@ public ResponseEntity getLast() throws IOException { } ) @GetMapping("/all") - public ResponseEntity> getAll() { - final var respList = getStorageAsList(pdu -> true); + public ResponseEntity> getAll(@RequestParam(required = false) final String format) { + final var respList = getStorageAsList(pdu -> true, format); return ResponseEntity.ok(respList); } @Operation(summary = "Get all X3 items from the X2X3 Storage") + @Parameters( + value = { + @Parameter( + name = "format", + schema = @Schema(allowableValues = { "java", "json", "hex", "base64", "" }, defaultValue = "base64"), + description = "The format of each element in the response" + ), + } + ) @ApiResponses( value = { @ApiResponse( @@ -90,8 +121,8 @@ public ResponseEntity> getAll() { } ) @GetMapping("/all/x3") - public ResponseEntity> getAllX3() { - final var respList = getStorageAsList(pdu -> PduType.X3_PDU.equals(pdu.pduType())); + public ResponseEntity> getAllX3(@RequestParam(required = false) final String format) { + final var respList = getStorageAsList(pdu -> PduType.X3_PDU.equals(pdu.pduType()), format); return ResponseEntity.ok(respList); } @@ -148,23 +179,54 @@ public Optional findSequenceNumber(final PduObject pdu) { // ================================ - private List getStorageAsList(final Predicate filter) { - return x2X3Memory - .getStorage() - .stream() - .filter(filter) - .map(pdu -> { + private List getStorageAsList(final Predicate filter, final String format) { + return x2X3Memory.getStorage().stream().filter(filter).map(pdu -> formatPdu(pdu, format)).toList(); + } + + static String formatPdu(final PduObject pdu, final String format) { + switch (format) { + case "java" -> { + return pdu.toString(); + } + case "json" -> { + final var objectMapper = new ObjectMapper(); + try { + return objectMapper.writeValueAsString(pdu); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + case "hex" -> { + final var pduBytes = new ByteArrayOutputStream(); + final var pduStream = new DataOutputStream(pduBytes); + try { + pdu.writeTo(pduStream); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return byteArrayToHex(pduBytes.toByteArray()); + } + case null, default -> { final var pduBytes = new ByteArrayOutputStream(); final var pduStream = new DataOutputStream(pduBytes); - try { pdu.writeTo(pduStream); } catch (final IOException e) { throw new RuntimeException(e); } - return Base64.getEncoder().encodeToString(pduBytes.toByteArray()); - }) - .toList(); + } + } + } + + private static String byteArrayToHex(final byte[] a) { + final var sb = new StringBuilder(a.length * 3); + for (var i = 0; i < a.length; i++) { + sb.append(String.format("%02x", a[i])); + if ((i + 1) % 4 == 0 && i + 1 < a.length) { + sb.append(" "); + } + } + return sb.toString(); } } diff --git a/src/test/java/com/sipgate/li/simulator/controller/X2X3ControllerTest.java b/src/test/java/com/sipgate/li/simulator/controller/X2X3ControllerTest.java new file mode 100644 index 0000000..587304b --- /dev/null +++ b/src/test/java/com/sipgate/li/simulator/controller/X2X3ControllerTest.java @@ -0,0 +1,66 @@ +package com.sipgate.li.simulator.controller; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.sipgate.li.lib.x2x3.protocol.PayloadDirection; +import com.sipgate.li.lib.x2x3.protocol.PayloadFormat; +import com.sipgate.li.lib.x2x3.protocol.PduObject; +import com.sipgate.li.lib.x2x3.protocol.PduType; +import com.sipgate.li.lib.x2x3.protocol.tlv.TLV; +import java.util.UUID; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class X2X3ControllerTest { + + private final PduObject pdu = new PduObject( + (short) 0, + (short) 5, + PduType.X3_PDU, + PayloadFormat.RTP, + PayloadDirection.SENT_FROM_TARGET, + UUID.fromString("62cd13da-7797-468c-befd-77f05008c996"), + new byte[] { 0, 1, 2, 3, 4, 5, 6, 7 }, + new TLV[0], + new byte[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53 } + ); + + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = { "base64" }) + void it_formats_pdu_to_base64(final String format) { + final var actual = X2X3Controller.formatPdu(pdu, format); + assertThat(actual).isEqualTo("AAUAAgAAACgAAAAQAAgAA2LNE9p3l0aMvv138FAIyZYAAQIDBAUGBwIDBQcLDRETFx0fJSkrLzU="); + } + + @Test + void it_formats_pdu_to_java() { + final var actual = X2X3Controller.formatPdu(pdu, "java"); + final var fixedActual = actual.replaceAll("@\\w+", "@(addr)"); + assertThat(fixedActual).isEqualTo( + "PduObject[majorVersion=0, minorVersion=5, pduType=X3_PDU, payloadFormat=RTP, payloadDirection=SENT_FROM_TARGET, xid=62cd13da-7797-468c-befd-77f05008c996, correlationID=[B@(addr), conditionalAttributeFields=[Lcom.sipgate.li.lib.x2x3.protocol.tlv.TLV;@(addr), payload=[B@(addr)]" + ); + } + + @Test + void it_formats_pdu_to_json() { + final var actual = X2X3Controller.formatPdu(pdu, "json"); + assertThat(actual).isEqualTo( + """ + {"majorVersion":0,"minorVersion":5,"pduType":"X3_PDU","payloadFormat":"RTP","payloadDirection":"SENT_FROM_TARGET","xid":"62cd13da-7797-468c-befd-77f05008c996","correlationID":"AAECAwQFBgc=","conditionalAttributeFields":[],"payload":"AgMFBwsNERMXHR8lKSsvNQ=="} + """.trim() + ); + } + + @Test + void it_formats_pdu_to_hex() { + final var actual = X2X3Controller.formatPdu(pdu, "hex"); + assertThat(actual).isEqualTo( + """ + 00050002 00000028 00000010 00080003 62cd13da 7797468c befd77f0 5008c996 00010203 04050607 02030507 0b0d1113 171d1f25 292b2f35 + """.trim() + ); + } +}