Skip to content

Commit

Permalink
Add support for CFKeyedArchiverUIDs.
Browse files Browse the repository at this point in the history
Closes DHowett#5.
  • Loading branch information
DHowett committed Mar 20, 2017
1 parent e789922 commit e9d7568
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 3 deletions.
14 changes: 13 additions & 1 deletion bplist.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ func (p *bplistGenerator) writePlistValue(pval cfValue) {
p.writeDataTag([]byte(pval))
case cfDate:
p.writeDateTag(time.Time(pval))
case cfUID:
p.writeUIDTag(UID(pval))
default:
panic(fmt.Errorf("unknown plist type %t", pval))
}
}

Expand Down Expand Up @@ -201,6 +205,14 @@ func (p *bplistGenerator) writeIntTag(n uint64) {
binary.Write(p.writer, binary.BigEndian, val)
}

func (p *bplistGenerator) writeUIDTag(u UID) {
nbytes := minimumSizeForInt(uint64(u))
tag := uint8(bpTagUID | (nbytes - 1))

binary.Write(p.writer, binary.BigEndian, tag)
p.writeSizedInt(uint64(u), nbytes)
}

func (p *bplistGenerator) writeRealTag(n float64, bits int) {
var tag uint8 = bpTagReal | 0x3
var val interface{} = n
Expand Down Expand Up @@ -523,7 +535,7 @@ func (p *bplistParser) parseTagAtOffset(off int64) cfValue {
return cfString(runes)
case bpTagUID: // Somehow different than int: low half is nbytes - 1 instead of log2(nbytes)
val, _ := p.readSizedInt(int(tag&0xF) + 1)
return &cfNumber{signed: false, value: val}
return cfUID(val)
case bpTagDictionary:
cnt := p.countForTag(tag)

Expand Down
43 changes: 43 additions & 0 deletions common_data_for_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,49 @@ var tests = []TestData{
BinaryFormat: []byte{98, 112, 108, 105, 115, 116, 48, 48, 209, 1, 2, 80, 85, 72, 101, 108, 108, 111, 8, 11, 12, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18},
},
},
{
Name: "CF Keyed Archiver UIDs (interface{})",
Data: []UID{
0xff,
0xffff,
0xffffff,
0xffffffff,
0xffffffffff,
},
Expected: map[int][]byte{
XMLFormat: []byte(xmlPreamble + `<plist version="1.0"><array><dict><key>CF$UID</key><integer>255</integer></dict><dict><key>CF$UID</key><integer>65535</integer></dict><dict><key>CF$UID</key><integer>16777215</integer></dict><dict><key>CF$UID</key><integer>4294967295</integer></dict><dict><key>CF$UID</key><integer>1099511627775</integer></dict></array></plist>`),
BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xa5, 0x01, 0x02, 0x03, 0x04, 0x05, 0x80, 0xff, 0x81, 0xff, 0xff, 0x83, 0x00, 0xff, 0xff, 0xff, 0x83, 0xff, 0xff, 0xff, 0xff, 0x87, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x0e, 0x10, 0x13, 0x18, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26},
},
},
{
Name: "CF Keyed Archiver UID (struct)",
Data: struct {
U UID `plist:"identifier"`
}{
U: 1024,
},
Expected: map[int][]byte{
XMLFormat: []byte(xmlPreamble + `<plist version="1.0"><dict><key>identifier</key><dict><key>CF$UID</key><integer>1024</integer></dict></dict></plist>`),
BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd1, 0x01, 0x02, 0x5a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x81, 0x04, 0x00, 0x08, 0x0b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19},
},
},
{
Name: "CF Keyed Archiver UID as Legacy Int",
Data: struct {
U UID `plist:"identifier"`
}{
U: 1024,
},
Expected: map[int][]byte{
XMLFormat: []byte(xmlPreamble + `<plist version="1.0"><dict><key>identifier</key><dict><key>CF$UID</key><integer>1024</integer></dict></dict></plist>`),
BinaryFormat: []byte{0x62, 0x70, 0x6c, 0x69, 0x73, 0x74, 0x30, 0x30, 0xd1, 0x01, 0x02, 0x5a, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x81, 0x04, 0x00, 0x08, 0x0b, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x19},
},
DecodeData: struct {
U uint64 `plist:"identifier"`
}{
U: 1024,
},
},
}

type EverythingTestData struct {
Expand Down
1 change: 1 addition & 0 deletions decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func NewDecoder(r io.ReadSeeker) *Decoder {
// in the interface value. If the interface value is nil, Unmarshal stores one of the following in the interface value:
//
// string, bool, uint64, float64
// plist.UID for "CoreFoundation Keyed Archiver UIDs" (convertible to uint64)
// []byte, for plist data
// []interface{}, for plist arrays
// map[string]interface{}, for plist dictionaries
Expand Down
4 changes: 4 additions & 0 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ func (p *Encoder) marshal(val reflect.Value) cfValue {

typ := val.Type()

if typ == uidType {
return cfUID(val.Uint())
}

if val.Kind() == reflect.Struct {
return p.marshalStruct(typ, val)
}
Expand Down
6 changes: 6 additions & 0 deletions plist.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,9 @@ func (e plistParseError) Error() string {
}
return s
}

// A UID represents a unique object identifier. UIDs are serialized in a manner distinct from
// that of integers.
//
// UIDs cannot be serialized in OpenStepFormat or GNUStepFormat property lists.
type UID uint64
2 changes: 0 additions & 2 deletions plist_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,3 @@ func (cfDate) typeName() string {
func (p cfDate) hash() interface{} {
return time.Time(p)
}

type UID uint64
16 changes: 16 additions & 0 deletions unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ func (u *incompatibleDecodeTypeError) Error() string {

var (
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
uidType = reflect.TypeOf(UID(0))
)

func isEmptyInterface(v reflect.Value) bool {
Expand Down Expand Up @@ -160,6 +161,19 @@ func (p *Decoder) unmarshal(pval cfValue, val reflect.Value) {
} else {
panic(incompatibleTypeError)
}
case cfUID:
if val.Type() == uidType {
val.SetUint(uint64(pval))
} else {
switch val.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val.SetInt(int64(pval))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
val.SetUint(uint64(pval))
default:
panic(incompatibleTypeError)
}
}
case *cfArray:
p.unmarshalArray(pval, val)
case *cfDictionary:
Expand Down Expand Up @@ -268,6 +282,8 @@ func (p *Decoder) valueInterface(pval cfValue) interface{} {
return []byte(pval)
case cfDate:
return time.Time(pval)
case cfUID:
return UID(pval)
}
return nil
}
Expand Down
18 changes: 18 additions & 0 deletions xml.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ func (p *xmlPlistGenerator) writePlistValue(pval cfValue) {
} else if a, ok := pval.(*cfArray); ok {
p.writeArray(a)
return
} else if uid, ok := pval.(cfUID); ok {
p.writeDictionary(&cfDictionary{
keys: []string{"CF$UID"},
values: []cfValue{
&cfNumber{
signed: false,
value: uint64(uid),
},
},
})
return
}

// Everything here and beyond is encoded the same way: <key>value</key>
Expand Down Expand Up @@ -300,6 +311,13 @@ func (p *xmlPlistParser) parseXMLElement(element xml.StartElement) cfValue {
}
}
}

if len(keys) == 1 && keys[0] == "CF$UID" && len(values) == 1 {
if integer, ok := values[0].(*cfNumber); ok {
return cfUID(integer.value)
}
}

return &cfDictionary{keys: keys, values: values}
case "array":
p.ntags++
Expand Down

0 comments on commit e9d7568

Please sign in to comment.