Skip to content

Commit 4f8a886

Browse files
xaionaro@dx.centerxaionaro@dx.center
authored andcommitted
Merge branch 'feature/transaction-resolve'
2 parents 98695b8 + 89d852c commit 4f8a886

6 files changed

Lines changed: 401 additions & 12 deletions

File tree

binder/versionaware/transport.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,5 +1117,13 @@ func (t *Transport) APILevel() int {
11171117
return t.apiLevel
11181118
}
11191119

1120+
// ActiveTable returns the current version table.
1121+
// The returned map must not be modified by the caller.
1122+
func (t *Transport) ActiveTable() VersionTable {
1123+
t.mu.RLock()
1124+
defer t.mu.RUnlock()
1125+
return t.table
1126+
}
1127+
11201128
// Verify Transport implements binder.VersionAwareTransport.
11211129
var _ binder.VersionAwareTransport = (*Transport)(nil)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package versionaware
2+
3+
import (
4+
"testing"
5+
6+
"github.com/AndroidGoLab/binder/binder"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestTransport_ActiveTable(t *testing.T) {
11+
table := VersionTable{
12+
"android.app.IFoo": {
13+
"doThing": binder.FirstCallTransaction + 0,
14+
},
15+
}
16+
tr := &Transport{table: table}
17+
got := tr.ActiveTable()
18+
assert.Equal(t, binder.FirstCallTransaction, got.Resolve("android.app.IFoo", "doThing"))
19+
}

cmd/bindercli/cmd_service.go

Lines changed: 150 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/spf13/cobra"
1414

1515
"github.com/AndroidGoLab/binder/binder"
16+
"github.com/AndroidGoLab/binder/binder/versionaware"
1617
"github.com/AndroidGoLab/binder/cmd/bindercli/discovery"
1718
"github.com/AndroidGoLab/binder/parcel"
1819
"github.com/AndroidGoLab/binder/servicemanager"
@@ -28,6 +29,7 @@ func newServiceCmd() *cobra.Command {
2829
cmd.AddCommand(newServiceInspectCmd())
2930
cmd.AddCommand(newServiceTransactCmd())
3031
cmd.AddCommand(newServiceMethodsCmd())
32+
cmd.AddCommand(newServiceResolveCmd())
3133

3234
return cmd
3335
}
@@ -125,16 +127,21 @@ func newServiceInspectCmd() *cobra.Command {
125127

126128
func newServiceTransactCmd() *cobra.Command {
127129
return &cobra.Command{
128-
Use: "transact <name> <code> [hex-data]",
130+
Use: "transact <name> <code-or-method> [hex-data]",
129131
Short: "Send a raw transaction to a binder service",
130132
Args: cobra.RangeArgs(2, 3),
131133
RunE: func(cmd *cobra.Command, args []string) error {
132134
ctx := cmd.Context()
133135
name := args[0]
136+
codeArg := args[1]
134137

135-
code, err := strconv.ParseUint(args[1], 0, 32)
136-
if err != nil {
137-
return fmt.Errorf("parsing transaction code: %w", err)
138+
var txCode binder.TransactionCode
139+
isNumeric := false
140+
141+
// Try parsing as numeric transaction code first.
142+
if parsed, parseErr := strconv.ParseUint(codeArg, 0, 32); parseErr == nil {
143+
txCode = binder.TransactionCode(parsed)
144+
isNumeric = true
138145
}
139146

140147
var data *parcel.Parcel
@@ -159,12 +166,28 @@ func newServiceTransactCmd() *cobra.Command {
159166
return err
160167
}
161168

162-
reply, err := svc.Transact(
163-
ctx,
164-
binder.TransactionCode(code),
165-
0,
166-
data,
167-
)
169+
// If not numeric, resolve method name to code.
170+
if !isNumeric {
171+
descriptor, descErr := descriptorForBinder(ctx, svc, name)
172+
if descErr != nil {
173+
return fmt.Errorf("resolving method name %q: %w", codeArg, descErr)
174+
}
175+
176+
table, tableErr := getActiveTable(conn)
177+
if tableErr != nil {
178+
return fmt.Errorf("resolving method name %q: %w", codeArg, tableErr)
179+
}
180+
181+
methodName := kebabToMethod(codeArg, descriptor)
182+
183+
resolved, ok := resolveMethodToCode(table, descriptor, methodName)
184+
if !ok {
185+
return fmt.Errorf("method %q not found on interface %s", codeArg, descriptor)
186+
}
187+
txCode = resolved
188+
}
189+
190+
reply, err := svc.Transact(ctx, txCode, 0, data)
168191
if err != nil {
169192
return fmt.Errorf("transact failed: %w", err)
170193
}
@@ -236,17 +259,25 @@ func newServiceMethodsCmd() *cobra.Command {
236259
return fmt.Errorf("unknown interface %q for service %q — not in registry", descriptor, name)
237260
}
238261

262+
table, tableErr := getActiveTable(conn)
263+
239264
f := NewFormatter(mode, os.Stdout)
240265
switch f.Mode {
241266
case "json":
242267
f.WriteJSON(map[string]any{
243268
"descriptor": descriptor,
244-
"methods": methodsToJSON(info.Methods),
269+
"methods": methodsToJSONWithCodes(info.Methods, table, descriptor),
245270
})
246271
default:
247272
fmt.Fprintf(f.W, "Interface: %s (%d methods)\n\n", descriptor, len(info.Methods))
248273
for _, m := range info.Methods {
249-
fmt.Fprintf(f.W, " %s\n", formatMethodSignature(m))
274+
codeStr := " ???? "
275+
if tableErr == nil {
276+
if code, ok := resolveMethodToCode(table, descriptor, m.Name); ok {
277+
codeStr = fmt.Sprintf(" 0x%04x", code)
278+
}
279+
}
280+
fmt.Fprintf(f.W, "%s %s\n", codeStr, formatMethodSignature(m))
250281
}
251282
}
252283

@@ -305,3 +336,110 @@ func methodsToJSON(methods []MethodInfo) []map[string]any {
305336
}
306337
return result
307338
}
339+
340+
func methodsToJSONWithCodes(
341+
methods []MethodInfo,
342+
table versionaware.VersionTable,
343+
descriptor string,
344+
) []map[string]any {
345+
result := make([]map[string]any, 0, len(methods))
346+
for _, m := range methods {
347+
entry := map[string]any{
348+
"name": camelToKebab(m.Name),
349+
}
350+
351+
if table != nil {
352+
if code, ok := resolveMethodToCode(table, descriptor, m.Name); ok {
353+
entry["code"] = fmt.Sprintf("0x%04x", code)
354+
}
355+
}
356+
357+
if len(m.Params) > 0 {
358+
params := make([]map[string]string, 0, len(m.Params))
359+
for _, p := range m.Params {
360+
params = append(params, map[string]string{
361+
"name": p.Name,
362+
"type": p.Type,
363+
})
364+
}
365+
entry["params"] = params
366+
}
367+
368+
if m.ReturnType != "" {
369+
entry["return_type"] = m.ReturnType
370+
}
371+
372+
result = append(result, entry)
373+
}
374+
return result
375+
}
376+
377+
func newServiceResolveCmd() *cobra.Command {
378+
return &cobra.Command{
379+
Use: "resolve <name> <method-or-code>",
380+
Short: "Resolve a transaction code or method name for a binder service",
381+
Long: `Resolve translates between method names and transaction codes.
382+
383+
If the second argument is a number, it is treated as a transaction code and
384+
resolved to a method name. Otherwise it is treated as a method name (in
385+
camelCase or kebab-case) and resolved to a transaction code.`,
386+
Args: cobra.ExactArgs(2),
387+
RunE: func(cmd *cobra.Command, args []string) error {
388+
ctx := cmd.Context()
389+
name := args[0]
390+
query := args[1]
391+
392+
conn, err := OpenConn(ctx, cmd)
393+
if err != nil {
394+
return err
395+
}
396+
defer conn.Close(ctx)
397+
398+
descriptor, err := resolveDescriptor(ctx, conn, name)
399+
if err != nil {
400+
return err
401+
}
402+
403+
table, err := getActiveTable(conn)
404+
if err != nil {
405+
return err
406+
}
407+
408+
mode, err := cmd.Root().PersistentFlags().GetString("format")
409+
if err != nil {
410+
return fmt.Errorf("reading --format flag: %w", err)
411+
}
412+
413+
f := NewFormatter(mode, os.Stdout)
414+
415+
// Try parsing as numeric transaction code first.
416+
if code, parseErr := strconv.ParseUint(query, 0, 32); parseErr == nil {
417+
methodName, ok := resolveCodeToMethod(table, descriptor, binder.TransactionCode(code))
418+
if !ok {
419+
return fmt.Errorf("no method found for code %#x on %s", code, descriptor)
420+
}
421+
f.Result(map[string]any{
422+
"descriptor": descriptor,
423+
"method": camelToKebab(methodName),
424+
"code": fmt.Sprintf("0x%04x", code),
425+
})
426+
return nil
427+
}
428+
429+
// Treat as method name -- support kebab-case via registry lookup.
430+
methodName := kebabToMethod(query, descriptor)
431+
432+
code, ok := resolveMethodToCode(table, descriptor, methodName)
433+
if !ok {
434+
return fmt.Errorf("method %q not found on %s", query, descriptor)
435+
}
436+
437+
f.Result(map[string]any{
438+
"descriptor": descriptor,
439+
"method": camelToKebab(methodName),
440+
"code": fmt.Sprintf("0x%04x", code),
441+
})
442+
return nil
443+
},
444+
}
445+
}

cmd/bindercli/cmd_service_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import (
77

88
"github.com/stretchr/testify/assert"
99
"github.com/stretchr/testify/require"
10+
11+
"github.com/AndroidGoLab/binder/binder"
12+
"github.com/AndroidGoLab/binder/binder/versionaware"
1013
)
1114

1215
func TestFormatMethodSignature_NoParams_NoReturn(t *testing.T) {
@@ -76,3 +79,36 @@ func TestMethodsToJSON(t *testing.T) {
7679
assert.Nil(t, result[2]["return_type"])
7780
assert.Nil(t, result[2]["params"])
7881
}
82+
83+
func TestMethodsToJSONWithCodes(t *testing.T) {
84+
methods := []MethodInfo{
85+
{Name: "DoThing", ReturnType: "bool"},
86+
{Name: "DoOther"},
87+
}
88+
table := versionaware.VersionTable{
89+
"android.app.IFoo": {
90+
"DoThing": binder.FirstCallTransaction + 0,
91+
"DoOther": binder.FirstCallTransaction + 1,
92+
},
93+
}
94+
95+
result := methodsToJSONWithCodes(methods, table, "android.app.IFoo")
96+
require.Len(t, result, 2)
97+
98+
assert.Equal(t, "do-thing", result[0]["name"])
99+
assert.Equal(t, "0x0001", result[0]["code"])
100+
assert.Equal(t, "bool", result[0]["return_type"])
101+
102+
assert.Equal(t, "do-other", result[1]["name"])
103+
assert.Equal(t, "0x0002", result[1]["code"])
104+
}
105+
106+
func TestMethodsToJSONWithCodes_NilTable(t *testing.T) {
107+
methods := []MethodInfo{
108+
{Name: "DoThing", ReturnType: "bool"},
109+
}
110+
111+
result := methodsToJSONWithCodes(methods, nil, "android.app.IFoo")
112+
require.Len(t, result, 1)
113+
assert.Nil(t, result[0]["code"])
114+
}

0 commit comments

Comments
 (0)