content: package de.danoeh.antennapod.parser.media.id3;
  
  import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
  import de.danoeh.antennapod.parser.media.id3.model.TagHeader;
  import org.apache.commons.io.input.CountingInputStream;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.robolectric.RobolectricTestRunner;
  
  import java.io.ByteArrayInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.nio.charset.StandardCharsets;
  
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  
  @RunWith(RobolectricTestRunner.class)
  public class Id3ReaderTest {
      @Test
      public void testReadString() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_ISO,
              'T', 'e', 's', 't',
              0 // Null-terminated
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          String string = new ID3Reader(inputStream).readEncodingAndString(1000);
          assertEquals("Test", string);
      }
  
      @Test
      public void testReadMultipleStrings() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_ISO,
              'F', 'o', 'o',
              0, // Null-terminated
              ID3Reader.ENCODING_ISO,
              'B', 'a', 'r',
              0 // Null-terminated
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          ID3Reader reader = new ID3Reader(inputStream);
          assertEquals("Foo", reader.readEncodingAndString(1000));
          assertEquals("Bar", reader.readEncodingAndString(1000));
      }
  
      @Test
      public void testReadingLimit() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_ISO,
              'A', 'B', 'C', 'D'
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          ID3Reader reader = new ID3Reader(inputStream);
          assertEquals("ABC", reader.readEncodingAndString(4)); // Includes encoding
          assertEquals('D', reader.readByte());
      }
  
      @Test
      public void testReadUtf16RespectsBom() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_UTF16_WITH_BOM,
              (byte) 0xff, (byte) 0xfe, // BOM: Little-endian
              'A', 0, 'B', 0, 'C', 0,
              0, 0, // Null-terminated
              ID3Reader.ENCODING_UTF16_WITH_BOM,
              (byte) 0xfe, (byte) 0xff, // BOM: Big-endian
              0, 'D', 0, 'E', 0, 'F',
              0, 0, // Null-terminated
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          ID3Reader reader = new ID3Reader(inputStream);
          assertEquals("ABC", reader.readEncodingAndString(1000));
          assertEquals("DEF", reader.readEncodingAndString(1000));
      }
  
      @Test
      public void testReadUtf16NullPrefix() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_UTF16_WITH_BOM,
              (byte) 0xff, (byte) 0xfe, // BOM
              0x00, 0x01, // Latin Capital Letter A with macron (Ā)
              0, 0, // Null-terminated
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          String string = new ID3Reader(inputStream).readEncodingAndString(1000);
          assertEquals("Ā", string);
      }
  
      @Test
      public void testReadingLimitUtf16() throws IOException {
          byte[] data = {
              ID3Reader.ENCODING_UTF16_WITHOUT_BOM,
              'A', 0, 'B', 0, 'C', 0, 'D', 0
          };
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          ID3Reader reader = new ID3Reader(inputStream);
          reader.readEncodingAndString(6); // Includes encoding, produces broken string
          assertTrue("Should respect limit even if it breaks a symbol", reader.getPosition() <= 6);
      }
  
      @Test
      public void testReadTagHeader() throws IOException, ID3ReaderException {
          byte[] data = generateId3Header(23);
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          TagHeader header = new ID3Reader(inputStream).readTagHeader();
          assertEquals("ID3", header.getId());
          assertEquals(42, header.getVersion());
          assertEquals(23, header.getSize());
      }
  
      @Test
      public void testReadFrameHeader() throws IOException {
          byte[] data = generateFrameHeader("CHAP", 42);
          CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
          FrameHeader header = new ID3Reader(inputStream).readFrameHeader();
          assertEquals("CHAP", header.getId());
          assertEquals(42, header.getSize());
      }
  
      public static byte[] generateFrameHeader(String id, int size) {
          return concat(
              id.getBytes(StandardCharsets.ISO_8859_1), // Frame ID
              new byte[] {
                  (byte) (size >> 24), (byte) (size >> 16),
                  (byte) (size >> 8), (byte) (size), // Size
                  0, 0 // Flags
              });
      }
  
      static byte[] generateId3Header(int size) {
          return new byte[] {
                  'I', 'D', '3', // Identifier
                  0, 42, // Version
                  0, // Flags
                  (byte) (size >> 24), (byte) (size >> 16),
                  (byte) (size >> 8), (byte) (size), // Size
          };
      }
  
      static byte[] concat(byte[]... arrays) {
          ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
          try {
              for (byte[] array : arrays) {
                  outputStream.write(array);
              }
          } catch (IOException e) {
              fail(e.getMessage());
          }
          return outputStream.toByteArray();
      }
  }
  