Skip to content

Commit

Permalink
Overhauled the UID class.
Browse files Browse the repository at this point in the history
Base UIDs on BigInteger (Issue #38) and implement Comparable (Issue #80).
Encoding UIDs now returns variable length byte arrays (1, 2, 4, 8 or 16 bytes).
  • Loading branch information
3breadt committed Dec 17, 2022
1 parent 679cd44 commit e344eff
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 21 deletions.
116 changes: 95 additions & 21 deletions src/main/java/com/dd/plist/UID.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,38 +23,78 @@
package com.dd.plist;

import java.io.IOException;
import java.math.BigInteger;
import java.util.Objects;
import java.util.Optional;

/**
* The UID class holds a unique identifier.
* Only found in binary property lists that are keyed archives.
*
* @author Daniel Dreibrodt
*/
public class UID extends NSObject {

private final byte[] bytes;
public class UID extends NSObject implements Comparable<UID> {
private final BigInteger uid;
private final String name;

/**
* Creates a new UID instance.
* @param name The UID name.
*
* @param name The UID name.
* @param value The UID value.
* @throws NullPointerException If value is null.
* @throws IllegalArgumentException If value is negative or has a length of more than 128-bit.
*/
public UID(String name, BigInteger value) {
this.name = name;
this.uid = Objects.requireNonNull(value);
if (this.uid.bitLength() > 128) {
throw new IllegalArgumentException("The specified UID exceeds the maximum length of 128-bit.");
}
if (this.uid.signum() < 0) {
throw new IllegalArgumentException("The specified value is negative.");
}
}

/**
* Creates a new UID instance.
*
* @param name The UID name.
* @param bytes The UID value.
* @throws NullPointerException If bytes is null.
* @throws IllegalArgumentException If bytes represents a UID with a length of more than 128-bit (leading zero bytes are ignored).
*/
public UID(String name, byte[] bytes) {
this.name = name;
this.bytes = bytes;
this(name, new BigInteger(1, Objects.requireNonNull(bytes)));
}

/**
* Gets this instance's value.
* @return The UID's value.
*
* @return The UID's value in big-endian representation, encoded on 1, 2, 4, 8 or 16 bytes.
*/
public byte[] getBytes() {
return this.bytes;
byte[] data = this.uid.toByteArray();
if (data.length == 3) {
byte[] paddedData = new byte[4];
System.arraycopy(data, 0, paddedData, 1, 3);
data = paddedData;
} else if (data.length > 4 && data.length < 8) {
byte[] paddedData = new byte[8];
System.arraycopy(data, 0, paddedData, 8 - data.length, data.length);
data = paddedData;
} else if (data.length > 8 && data.length < 16) {
byte[] paddedData = new byte[16];
System.arraycopy(data, 0, paddedData, 16 - data.length, data.length);
data = paddedData;
}

return data;
}

/**
* Gets this instance's name.
*
* @return The UID's name.
*/
public String getName() {
Expand All @@ -63,7 +103,7 @@ public String getName() {

@Override
public UID clone() {
return new UID(this.name, this.bytes.clone());
return new UID(this.name, this.uid.toByteArray());
}

/**
Expand All @@ -77,34 +117,68 @@ public UID clone() {
void toXML(StringBuilder xml, int level) {
this.indent(xml, level);
xml.append("<string>");
for (byte b : this.bytes) {
if (b < 16)
xml.append('0');
xml.append(Integer.toHexString(b));
}
xml.append(this.uid.toString(16));
xml.append("</string>");
}

@Override
void toBinary(BinaryPropertyListWriter out) throws IOException {
out.write(0x80 + this.bytes.length - 1);
out.write(this.bytes);
byte[] bytes = this.getBytes();
out.write(0x80 + bytes.length - 1);
out.write(bytes);
}

@Override
protected void toASCII(StringBuilder ascii, int level) {
this.indent(ascii, level);
ascii.append('"');
for (byte b : this.bytes) {
if (b < 16)
ascii.append('0');
ascii.append(Integer.toHexString(b));
}
ascii.append(this.uid.toString(16));
ascii.append('"');
}

@Override
protected void toASCIIGnuStep(StringBuilder ascii, int level) {
this.toASCII(ascii, level);
}

@Override
public int compareTo(UID o) {
if (o == null) {
throw new NullPointerException();
}

int diff = this.uid.compareTo(o.uid);
if (diff == 0) {
if (this.name == null) {
return o.name != null ? 1 : 0;
}
else if (o.name == null) {
return -1;
}

return this.name.compareTo(o.name);
}

return diff;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || this.getClass() != o.getClass()) return false;
UID uid = (UID) o;
return this.uid.equals(((UID) o).uid) && Objects.equals(this.name, uid.name);
}

@Override
public int hashCode() {
int result = Objects.hash(this.name);
result = 31 * result + this.uid.hashCode();
return result;
}

@Override
public String toString() {
return this.uid.toString(16) + (this.name != null ? " (" + this.name + ")" : "");
}
}
7 changes: 7 additions & 0 deletions src/test/java/com/dd/plist/test/IssueTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.xml.sax.SAXParseException;

import java.io.*;
import java.math.BigInteger;
import java.nio.file.Files;
import java.text.ParseException;
import java.util.Arrays;
Expand Down Expand Up @@ -179,6 +180,12 @@ public void testIssue78_NullReferenceExceptionForInvalidNSDictionaryKey() {
assertThrows(PropertyListFormatException.class, () -> PropertyListParser.parse(plistFile));
}

@Test
public void testIssue80_ClassCastExceptionForUidAddedToSet() {
NSSet set = new NSSet(true);
assertDoesNotThrow(() -> set.addObject(new UID(null, BigInteger.valueOf(42))));
}

@ParameterizedTest
@MethodSource("provideIssue82ErrorFiles")
public void testIssue82_IndexOutOfBoundsExceptions(File file) {
Expand Down
87 changes: 87 additions & 0 deletions src/test/java/com/dd/plist/test/UidTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package com.dd.plist.test;

import com.dd.plist.UID;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class UidTests {

@Test
public void constructoRejectsTooLargeValues()
{
byte[] data = new byte[17];
data[0] = 0x01;
assertThrows(IllegalArgumentException.class, () -> new UID(null, data));
}

@Test
public void getBytes() {
assertEquals(1, new UID(null, new byte[]{0x01}).getBytes().length);
assertEquals(2, new UID(null, new byte[]{0x01, 0x00}).getBytes().length);
assertEquals(4, new UID(null, new byte[]{0x01, 0x00, 0x00}).getBytes().length);
assertEquals(4, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(8, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(8, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(8, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(8, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
assertEquals(16, new UID(null, new byte[]{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}).getBytes().length);
}

@ParameterizedTest
@MethodSource("provideUIDs")
public void compareToAndEquals(UID a, UID b, int expectation) {
assertEquals(expectation, a.compareTo(b), "compareTo returned unexpected value");
assertEquals(expectation * -1, b.compareTo(a), "compareTo returned unexpected value");
assertEquals(expectation == 0, a.equals(b), "equals returned unexpected value");
}

private static Stream<Arguments> provideUIDs() {
return Stream.of(
Arguments.of(
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
0),
Arguments.of(
new UID(null, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
new UID("", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
1),
Arguments.of(
new UID("a", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
new UID("b", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
-1),
Arguments.of(
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}),
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}),
1),
Arguments.of(
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}),
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}),
1),
Arguments.of(
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}),
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}),
1),
Arguments.of(
new UID("x", new byte[]{0x12, 0x34, 0x56, 0x78, 0x09}),
new UID("x", new byte[]{0x12, 0x34, 0x55, 0x78, 0x09}),
1),
Arguments.of(
new UID("x", new byte[]{0x12, 0x34, 0x56, 0x78, 0x09}),
new UID("x", new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00}),
1));
}
}

0 comments on commit e344eff

Please sign in to comment.