forked from oandrew/ipod
-
Notifications
You must be signed in to change notification settings - Fork 1
/
lingo.go
207 lines (180 loc) · 4.56 KB
/
lingo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package ipod
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// LingoCmdID represents Lingo ID and Command ID
type LingoCmdID uint32
func (id LingoCmdID) LingoID() uint8 {
return uint8(id >> 16 & 0xff)
}
func (id LingoCmdID) CmdID() uint16 {
return uint16(id & 0xffff)
}
func (id LingoCmdID) GoString() string {
return fmt.Sprintf("(%#02x|%#0*x)", id.LingoID(), cmdIDLen(id.LingoID())*2, id.CmdID())
}
func (id LingoCmdID) String() string {
return fmt.Sprintf("%#02x,%#0*x", id.LingoID(), cmdIDLen(id.LingoID())*2, id.CmdID())
}
func cmdIDLen(lingoID uint8) int {
switch lingoID {
case LingoExtRemoteID:
return 2
default:
return 1
}
}
func marshalLingoCmdID(w io.Writer, id LingoCmdID) error {
err := binary.Write(w, binary.BigEndian, byte(id.LingoID()))
if err != nil {
return err
}
switch cmdIDLen(id.LingoID()) {
case 2:
return binary.Write(w, binary.BigEndian, uint16(id.CmdID()))
default:
return binary.Write(w, binary.BigEndian, byte(id.CmdID()))
}
}
func unmarshalLingoCmdID(r io.Reader, id *LingoCmdID) error {
var lingoID uint8
err := binary.Read(r, binary.BigEndian, &lingoID)
if err != nil {
return err
}
switch cmdIDLen(lingoID) {
case 2:
var cmdID uint16
err := binary.Read(r, binary.BigEndian, &cmdID)
if err != nil {
return err
}
*id = NewLingoCmdID(uint16(lingoID), uint16(cmdID))
return nil
default:
var cmdID uint8
err := binary.Read(r, binary.BigEndian, &cmdID)
if err != nil {
return err
}
*id = NewLingoCmdID(uint16(lingoID), uint16(cmdID))
return nil
}
}
func NewLingoCmdID(lingo, cmd uint16) LingoCmdID {
return LingoCmdID(uint32(lingo)<<16 | uint32(cmd))
}
func parseIdTag(tag *reflect.StructTag) (uint16, error) {
id, err := strconv.ParseUint(tag.Get("id"), 0, 16)
return uint16(id), err
}
var mIDToType = make(map[LingoCmdID][]reflect.Type)
var mTypeToID = make(map[reflect.Type]LingoCmdID)
func storeMapping(cmd LingoCmdID, t reflect.Type) {
mIDToType[cmd] = append(mIDToType[cmd], t)
mTypeToID[t] = cmd
}
// RegisterLingos registers a set of lingo commands
func RegisterLingos(lingoID uint8, m interface{}) error {
lingos := reflect.TypeOf(m)
for i := 0; i < lingos.NumField(); i++ {
cmd := lingos.Field(i)
cmdID, err := parseIdTag(&cmd.Tag)
if err != nil {
return fmt.Errorf("register lingos: parse id tag err: %v", err)
}
storeMapping(NewLingoCmdID(uint16(lingoID), cmdID), cmd.Type)
}
return nil
}
// DumpLingos returns a list of all registered lingos and commands
func DumpLingos() string {
type cmd struct {
id LingoCmdID
name string
}
var cmds []cmd
for id, types := range mIDToType {
cmds = append(cmds, cmd{id, types[0].String()})
}
sort.Slice(cmds, func(i, j int) bool {
return cmds[i].id < cmds[j].id
})
buf := bytes.Buffer{}
for _, cmd := range cmds {
fmt.Fprintf(&buf, "%s\t%s\n", cmd.id.GoString(), cmd.name)
}
return buf.String()
}
// LookupID finds a registered LingoCmdID by the type of v
// i.e. reverse to Lookup
func LookupID(v interface{}) (id LingoCmdID, ok bool) {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Ptr {
panic(fmt.Sprintf("payload is not pointer: %v", v))
}
id, ok = mTypeToID[t.Elem()]
return
}
// LookupResult contains the result of a Lookup.
// Payload is a pointer to a new zero value of the found type
// Transaction specifies if the Transaction should be present in the packet.
type LookupResult struct {
Payload interface{}
Transaction bool
}
// Lookup finds a the payload by LingoCmdID using payloadSize as a hint
func Lookup(id LingoCmdID, payloadSize int, defaultTrxEnabled bool) (LookupResult, bool) {
payloads, ok := mIDToType[id]
if !ok {
return LookupResult{}, false
}
for _, p := range payloads {
v := reflect.New(p).Interface()
cmdSize := binarySize(v)
if cmdSize == -1 {
continue
}
switch cmdSize {
case payloadSize:
return LookupResult{
Payload: v,
Transaction: false,
}, true
case payloadSize - 2:
return LookupResult{
Payload: v,
Transaction: true,
}, true
}
}
// else assume transaction=true
if len(payloads) == 1 {
return LookupResult{
Payload: reflect.New(payloads[0]).Interface(),
Transaction: defaultTrxEnabled,
}, true
}
return LookupResult{}, false
}
func binarySize(v interface{}) int {
return binary.Size(v)
}
const (
LingoGeneralID = 0x00
LingoSimpleRemoteID = 0x02
LingoDisplayRemoteID = 0x03
LingoExtRemoteID = 0x04
LingoUSBHostID = 0x06
LingoRFTunerID = 0x07
LingoEqID = 0x08
LingoSportsID = 0x09
LingoDigitalAudioID = 0x0A
LingoStorageID = 0x0C
)