Skip to content

Commit e8aec3e

Browse files
committed
Another KFX cleanup.
1 parent ae6c636 commit e8aec3e

File tree

3 files changed

+266
-235
lines changed

3 files changed

+266
-235
lines changed

thumbs/kfx/ion.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ package kfx
22

33
import (
44
"bytes"
5+
"encoding/binary"
56
"fmt"
7+
"io"
68

79
"github.com/amazon-ion/ion-go/ion"
810
)
911

1012
const (
1113
largestKnownSymbol = 834
12-
//
13-
symBookMetadata = 490
14-
symExternalResource = 164
15-
symRawMedia = 417
1614
)
1715

1816
var (
@@ -38,14 +36,38 @@ func createProlog() []byte {
3836
return buf.Bytes()
3937
}
4038

41-
func decodeData(prolog, data []byte, v any) error {
39+
func readDataFrom(r io.Reader, v any) error {
40+
if err := binary.Read(r, binary.LittleEndian, v); err != nil {
41+
return err
42+
}
43+
if val, ok := v.(interface{ validate() error }); ok {
44+
return val.validate()
45+
}
46+
return nil
47+
}
48+
49+
func readData(data []byte, v any) (int, error) {
50+
r := bytes.NewReader(data)
51+
if err := binary.Read(r, binary.LittleEndian, v); err != nil {
52+
return 0, err
53+
}
54+
if val, ok := v.(interface{ validate() error }); ok {
55+
return len(data) - r.Len(), val.validate()
56+
}
57+
return len(data) - r.Len(), nil
58+
}
59+
60+
func decodeIon(prolog, data []byte, v any) error {
4261
if err := ion.Unmarshal(append(prolog, data[len(ionBVM):]...), v, sharedSymbolTable); err != nil {
4362
return err
4463
}
64+
if val, ok := v.(interface{ validate() error }); ok {
65+
return val.validate()
66+
}
4567
return nil
4668
}
4769

48-
func decodeST(data []byte) (ion.SymbolTable, error) {
70+
func decodeSymbolTable(data []byte) (ion.SymbolTable, error) {
4971
r := ion.NewReaderCat(bytes.NewReader(data), ion.NewCatalog(sharedSymbolTable))
5072
r.Next() // we are not interested in the actual values and in most cases this will return false anyways
5173
if err := r.Err(); err != nil {

thumbs/kfx/kfx.go

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
package kfx
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"io"
7+
"slices"
8+
"unsafe"
9+
10+
"github.com/amazon-ion/ion-go/ion"
11+
)
12+
13+
const (
14+
symBookMetadata = 490
15+
symExternalResource = 164
16+
symRawMedia = 417
17+
)
18+
19+
type containerHeader struct {
20+
Signature [4]byte
21+
Version uint16
22+
Size uint32
23+
InfoOffset uint32
24+
InfoSize uint32
25+
}
26+
27+
func (c *containerHeader) validate() error {
28+
const (
29+
maxContVersion = 2
30+
)
31+
32+
if !bytes.Equal(c.Signature[:], []byte("CONT")) {
33+
return fmt.Errorf("wrong signature for KFX container: % X", c.Signature[:])
34+
}
35+
if c.Version > maxContVersion {
36+
return fmt.Errorf("unsupported KFX container version: %d", c.Version)
37+
}
38+
if uintptr(c.Size) < unsafe.Sizeof(c) {
39+
return fmt.Errorf("invalid KFX container header size: %d", c.Size)
40+
}
41+
return nil
42+
}
43+
44+
type containerInfo struct {
45+
ContainerId string `ion:"$409"`
46+
CompressionType int `ion:"$410"`
47+
DRMScheme int `ion:"$411"`
48+
ChunkSize int `ion:"$412"`
49+
IndexTabOffset int `ion:"$413"`
50+
IndexTabLength int `ion:"$414"`
51+
DocSymOffset int `ion:"$415"`
52+
DocSymLength int `ion:"$416"`
53+
FCapabilitiesOffset int `ion:"$594"`
54+
FCapabilitiesLength int `ion:"$595"`
55+
}
56+
57+
func (c *containerInfo) validate() error {
58+
const (
59+
defaultCompressionType = 0
60+
defaultDRMScheme = 0
61+
)
62+
63+
if c.CompressionType != defaultCompressionType {
64+
return fmt.Errorf("unsupported KFX container compression type: %d", c.CompressionType)
65+
}
66+
if c.DRMScheme != defaultDRMScheme {
67+
return fmt.Errorf("unsupported KFX container DRM: %d", c.DRMScheme)
68+
}
69+
return nil
70+
}
71+
72+
type indexTableEntry struct {
73+
NumID, NumType uint32
74+
Offset, Size uint64
75+
}
76+
77+
func (e *indexTableEntry) readFrom(r io.Reader, start uint32, limit int, st ion.SymbolTable) error {
78+
*e = indexTableEntry{}
79+
80+
if err := readDataFrom(r, e); err != nil {
81+
return err
82+
}
83+
if uint64(start)+e.Offset > uint64(limit) {
84+
return fmt.Errorf("entity is out of bounds: %d + %d > %d", uint64(start)+e.Offset, e.Size, limit)
85+
}
86+
if _, ok := st.FindByID(uint64(e.NumID)); !ok {
87+
return fmt.Errorf("entity ID not found in the symbol table: %d", e.NumID)
88+
}
89+
if _, ok := st.FindByID(uint64(e.NumType)); !ok {
90+
return fmt.Errorf("entity type not found in the symbol table: %d", e.NumType)
91+
}
92+
return nil
93+
}
94+
95+
type entityHeader struct {
96+
Signature [4]byte
97+
Version uint16
98+
Size uint32
99+
}
100+
101+
func (e *entityHeader) validate() error {
102+
const (
103+
maxEntityVersion = 1
104+
)
105+
106+
if !bytes.Equal(e.Signature[:], []byte("ENTY")) {
107+
return fmt.Errorf("wrong signature for KFX entity: % X", e.Signature[:])
108+
}
109+
if e.Version > maxEntityVersion {
110+
return fmt.Errorf("unsupported KFX entity version: %d", e.Version)
111+
}
112+
if uintptr(e.Size) < unsafe.Sizeof(e) {
113+
return fmt.Errorf("invalid KFX entity header size: %d", e.Size)
114+
}
115+
return nil
116+
}
117+
118+
type entityInfo struct {
119+
CompressionType int `ion:"$410"`
120+
DRMScheme int `ion:"$411"`
121+
}
122+
123+
func (e *entityInfo) validate() error {
124+
const (
125+
defaultCompressionType = 0
126+
defaultDRMScheme = 0
127+
)
128+
129+
if e.CompressionType != defaultCompressionType {
130+
return fmt.Errorf("unsupported KFX entity compression type: %d", e.CompressionType)
131+
}
132+
if e.DRMScheme != defaultDRMScheme {
133+
return fmt.Errorf("unsupported KFX entity DRM: %d", e.DRMScheme)
134+
}
135+
return nil
136+
}
137+
138+
type (
139+
entity struct {
140+
id, idType uint32
141+
data []byte
142+
}
143+
entitySet struct {
144+
st ion.SymbolTable
145+
fragments map[uint32][]*entity
146+
}
147+
)
148+
149+
func newEntitySet(st ion.SymbolTable) *entitySet {
150+
return &entitySet{
151+
st: st,
152+
fragments: make(map[uint32][]*entity),
153+
}
154+
}
155+
156+
func (s *entitySet) addEntity(id, idType uint32, data []byte) {
157+
s.fragments[idType] = append(s.fragments[idType],
158+
&entity{
159+
id: id,
160+
idType: idType,
161+
data: data,
162+
})
163+
}
164+
165+
func (s *entitySet) getAllOfType(idType uint32) []*entity {
166+
entities, exists := s.fragments[idType]
167+
if !exists {
168+
return nil
169+
}
170+
return entities
171+
}
172+
173+
func (s *entitySet) getByName(name string, idType uint32) (*entity, error) {
174+
entities, exists := s.fragments[idType]
175+
if !exists {
176+
return nil, fmt.Errorf("no entities of type '%d' found", idType)
177+
}
178+
id, ok := s.st.FindByName(name)
179+
if !ok {
180+
return nil, fmt.Errorf("symbol '%s' not found in the symbol table", name)
181+
}
182+
id -= ion.V1SystemSymbolTable.MaxID()
183+
184+
var index int
185+
if index = slices.IndexFunc(entities, func(e *entity) bool {
186+
return uint64(e.id) == id
187+
}); index == -1 {
188+
return nil, fmt.Errorf("entity fragment '%s' of type '%d' not found", name, idType)
189+
}
190+
return entities[index], nil
191+
}
192+
193+
type (
194+
property struct {
195+
Key string `ion:"$492"`
196+
Value any `ion:"$307"`
197+
}
198+
properties struct {
199+
Category string `ion:"$495"`
200+
Metadata []property `ion:"$258"`
201+
}
202+
bookMetadata struct {
203+
CategorizedMetadata []properties `ion:"$491"`
204+
}
205+
coverResource struct {
206+
Location string `ion:"$165"`
207+
// ResourceName any `ion:"$175"` // ion.SymbolToken
208+
// Format any `ion:"$161"` // ion.SymbolToken
209+
// Width int `ion:"$422"`
210+
// Height int `ion:"$423"`
211+
// Mime string `ion:"$162"`
212+
}
213+
)

0 commit comments

Comments
 (0)