Skip to content

Commit a62d79b

Browse files
Milestone v0.3.0: RNDIS Control Handshake verified on live Samsung device
1 parent 899ced6 commit a62d79b

7 files changed

Lines changed: 416 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@ Format:
2020

2121
---
2222

23+
## v0.3.0 — 2026-03-28
24+
25+
### 2026-03-28 13:51 — Validated RNDIS Handshake on Live Device
26+
- What: Confirmed RNDIS `INIT`, `QUERY(MAC)`, and `SET` handshake works; retrieved real device MAC address (`ee211e71ab6a`); refined `gousb` field access and control transfer reliability.
27+
- Why: Complete Milestone v0.3.0; the device is now fully initialized and ready to send/receive network packets.
28+
- Files: `internal/rndis/rndis.go`, `internal/usb/device.go`, `internal/daemon/daemon.go`
29+
- Breaking: no
30+
31+
### 2026-03-28 13:50 — Implement RNDIS Handshake State Machine
32+
- What: Built binary marshalling for `INIT/QUERY/SET` messages (`internal/rndis/messages.go`); implemented RNDIS state machine for `Handshake()` sequence; added `ControlCall` to `usb.Device`.
33+
- Why: Milestone v0.3.0; allow the daemon to put the phone into data mode and retrieve device MAC address.
34+
- Files: `internal/rndis/oids.go`, `internal/rndis/messages.go`, `internal/rndis/rndis.go`, `internal/usb/device.go`, `internal/daemon/daemon.go`
35+
- Breaking: no
36+
37+
---
38+
2339
## v0.2.0 — 2026-03-28
2440

2541
### 2026-03-28 13:15 — Validated USB RNDIS Detection on Live Device

VERSIONS.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ v1.0.0 = MVP complete and working on M1/M2/M3.
1010

1111
---
1212

13+
## v0.3.0 — 2026-03-28
14+
- Milestone: RNDIS handshake working (INIT/QUERY/SET)
15+
- What works: Raw USB Control transfers for RNDIS encapsulated commands; `INIT` handshake; `QUERY MAC` address retrieval; `SET` packet filter to enable promiscuous data mode. **Confirmed on real device.**
16+
- What's broken: No actual data transfer (bulk transfer) implemented yet.
17+
- Next: `internal/tun/utun.go` creation and bulk packet relay.
18+
19+
---
20+
1321
## v0.2.0 — 2026-03-28
1422
- Milestone: USB device detection + hotplug watcher
1523
- What works: `libusb` device monitoring, RNDIS hardware identification, and background daemon. **Validated on real Samsung Galaxy hardware.**

internal/daemon/daemon.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/rs/zerolog/log"
1111

1212
"github.com/princePal/droidtether/config"
13+
"github.com/princePal/droidtether/internal/rndis"
1314
"github.com/princePal/droidtether/internal/usb"
1415
)
1516

@@ -42,9 +43,15 @@ func (d *Daemon) Run() error {
4243
log.Info().
4344
Str("component", "daemon").
4445
Msg("Android RNDIS device connected!")
45-
// In the future:
46-
// session := NewSession(dev)
47-
// session.Start()
46+
47+
session := rndis.NewSession(dev)
48+
if err := session.Handshake(); err != nil {
49+
log.Error().Str("component", "daemon").Err(err).Msg("RNDIS Handshake failed")
50+
// Cleanup happens naturally via watcher detach
51+
}
52+
53+
// Wait a bit to verify logs in dev mode
54+
time.Sleep(3 * time.Second)
4855
})
4956

5057
watcher.OnDetach(func() {

internal/rndis/messages.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package rndis
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
)
7+
8+
// CommonHeader is the 8-byte prefix for all RNDIS Control Messages.
9+
type CommonHeader struct {
10+
MessageType uint32
11+
MessageLength uint32
12+
}
13+
14+
// RemoteNdisInitializeMsg is sent by Host -> Device to start the handshake.
15+
type RemoteNdisInitializeMsg struct {
16+
MessageType uint32 // MsgInit (0x02)
17+
MessageLength uint32 // 24 bytes
18+
RequestID uint32
19+
MajorVersion uint32 // 1
20+
MinorVersion uint32 // 0
21+
MaxTransferSz uint32 // Recommended: 16384 (16KB)
22+
}
23+
24+
func (m *RemoteNdisInitializeMsg) Marshal() []byte {
25+
b := make([]byte, 24)
26+
binary.LittleEndian.PutUint32(b[0:4], MsgInit)
27+
binary.LittleEndian.PutUint32(b[4:8], 24)
28+
binary.LittleEndian.PutUint32(b[8:12], m.RequestID)
29+
binary.LittleEndian.PutUint32(b[12:16], 1) // Major
30+
binary.LittleEndian.PutUint32(b[16:20], 0) // Minor
31+
binary.LittleEndian.PutUint32(b[20:24], m.MaxTransferSz)
32+
return b
33+
}
34+
35+
// RemoteNdisInitializeCmplt is sent by Device -> Host.
36+
type RemoteNdisInitializeCmplt struct {
37+
RequestID uint32
38+
Status uint32
39+
MajorVersion uint32
40+
MinorVersion uint32
41+
MaxTransfer uint32
42+
MaxPackets uint32
43+
PacketFormat uint32 // RNDIS_PACKET_FORMAT_ETH
44+
}
45+
46+
func UnmarshalInitializeCmplt(data []byte) (*RemoteNdisInitializeCmplt, error) {
47+
if len(data) < 52 {
48+
return nil, fmt.Errorf("init_cmplt: message too short (%d)", len(data))
49+
}
50+
// Verify it's a Completion for Init
51+
msgType := binary.LittleEndian.Uint32(data[0:4])
52+
if msgType != MsgInitCmplt {
53+
return nil, fmt.Errorf("init_cmplt: wrong message type 0x%08X", msgType)
54+
}
55+
56+
return &RemoteNdisInitializeCmplt{
57+
RequestID: binary.LittleEndian.Uint32(data[8:12]),
58+
Status: binary.LittleEndian.Uint32(data[12:16]),
59+
MajorVersion: binary.LittleEndian.Uint32(data[16:20]),
60+
MinorVersion: binary.LittleEndian.Uint32(data[20:24]),
61+
MaxTransfer: binary.LittleEndian.Uint32(data[32:36]),
62+
MaxPackets: binary.LittleEndian.Uint32(data[36:40]),
63+
PacketFormat: binary.LittleEndian.Uint32(data[44:48]),
64+
}, nil
65+
}
66+
67+
// RemoteNdisQueryMsg is sent by Host -> Device to query OIDs (e.g., getting MAC address).
68+
type RemoteNdisQueryMsg struct {
69+
RequestID uint32
70+
OID uint32
71+
InformationBufferLength uint32 // for simple queries, usually 0
72+
}
73+
74+
func (m *RemoteNdisQueryMsg) Marshal() []byte {
75+
b := make([]byte, 28)
76+
binary.LittleEndian.PutUint32(b[0:4], MsgQuery)
77+
binary.LittleEndian.PutUint32(b[4:8], 28)
78+
binary.LittleEndian.PutUint32(b[8:12], m.RequestID)
79+
binary.LittleEndian.PutUint32(b[12:16], m.OID)
80+
binary.LittleEndian.PutUint32(b[16:20], 0) // Info Buffer Length
81+
binary.LittleEndian.PutUint32(b[20:24], 20) // Info Buffer Offset (offset from RequestID field, which is 8 index + 12 = 20)
82+
binary.LittleEndian.PutUint32(b[24:28], 0) // Device Context
83+
return b
84+
}
85+
86+
// RemoteNdisQueryCmplt is sent by Device -> Host.
87+
type RemoteNdisQueryCmplt struct {
88+
RequestID uint32
89+
Status uint32
90+
Payload []byte
91+
}
92+
93+
func UnmarshalQueryCmplt(data []byte) (*RemoteNdisQueryCmplt, error) {
94+
if len(data) < 24 {
95+
return nil, fmt.Errorf("query_cmplt: message too short (%d)", len(data))
96+
}
97+
msgType := binary.LittleEndian.Uint32(data[0:4])
98+
if msgType != MsgQueryCmplt {
99+
return nil, fmt.Errorf("query_cmplt: wrong message type 0x%08X", msgType)
100+
}
101+
102+
infoLen := binary.LittleEndian.Uint32(data[16:20])
103+
infoOff := binary.LittleEndian.Uint32(data[20:24]) // Offset starts at byte 8 (RequestID)
104+
105+
start := int(8 + infoOff)
106+
end := start + int(infoLen)
107+
108+
if end > len(data) {
109+
return nil, fmt.Errorf("query_cmplt: payload out of bounds")
110+
}
111+
112+
return &RemoteNdisQueryCmplt{
113+
RequestID: binary.LittleEndian.Uint32(data[8:12]),
114+
Status: binary.LittleEndian.Uint32(data[12:16]),
115+
Payload: data[start:end],
116+
}, nil
117+
}
118+
119+
// RemoteNdisSetMsg is sent by Host -> Device to set OIDs (e.g., enable packet processing).
120+
type RemoteNdisSetMsg struct {
121+
RequestID uint32
122+
OID uint32
123+
Value uint32
124+
}
125+
126+
func (m *RemoteNdisSetMsg) Marshal() []byte {
127+
b := make([]byte, 32)
128+
binary.LittleEndian.PutUint32(b[0:4], MsgSet)
129+
binary.LittleEndian.PutUint32(b[4:8], 32)
130+
binary.LittleEndian.PutUint32(b[8:12], m.RequestID)
131+
binary.LittleEndian.PutUint32(b[12:16], m.OID)
132+
binary.LittleEndian.PutUint32(b[16:20], 4) // Value size (uint32)
133+
binary.LittleEndian.PutUint32(b[20:24], 20) // Value offset (from byte 8)
134+
binary.LittleEndian.PutUint32(b[24:28], 0) // Device Context
135+
binary.LittleEndian.PutUint32(b[28:32], m.Value)
136+
return b
137+
}
138+
139+
func UnmarshalSetCmplt(data []byte) (uint32, uint32, error) { // returns RequestID, Status
140+
if len(data) < 16 {
141+
return 0, 0, fmt.Errorf("set_cmplt: too short")
142+
}
143+
msgType := binary.LittleEndian.Uint32(data[0:4])
144+
if msgType != MsgSetCmplt {
145+
return 0, 0, fmt.Errorf("set_cmplt: wrong msg type")
146+
}
147+
return binary.LittleEndian.Uint32(data[8:12]), binary.LittleEndian.Uint32(data[12:16]), nil
148+
}

internal/rndis/oids.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package rndis
2+
3+
// RNDIS message types (Host -> Device)
4+
const (
5+
MsgInit uint32 = 0x00000002
6+
MsgQuery uint32 = 0x00000004
7+
MsgSet uint32 = 0x00000005
8+
MsgReset uint32 = 0x00000006
9+
MsgKeepAlive uint32 = 0x00000008
10+
MsgPacket uint32 = 0x00000001
11+
)
12+
13+
// RNDIS message completion types (Device -> Host)
14+
const (
15+
MsgInitCmplt uint32 = 0x80000002
16+
MsgQueryCmplt uint32 = 0x80000004
17+
MsgSetCmplt uint32 = 0x80000005
18+
MsgResetCmplt uint32 = 0x80000006
19+
MsgKeepAliveCmplt uint32 = 0x80000008
20+
)
21+
22+
// RNDIS Object Identifiers (OIDs)
23+
const (
24+
OID_GEN_SUPPORTED_LIST uint32 = 0x00010101
25+
OID_GEN_HARDWARE_STATUS uint32 = 0x00010102
26+
OID_GEN_MEDIA_SUPPORTED uint32 = 0x00010103
27+
OID_GEN_MEDIA_IN_USE uint32 = 0x00010104
28+
OID_GEN_MAXIMUM_LOOKAHEAD uint32 = 0x00010105
29+
OID_GEN_MAXIMUM_FRAME_SIZE uint32 = 0x00010106
30+
OID_GEN_LINK_SPEED uint32 = 0x00010107
31+
OID_GEN_TRANSMIT_BUFFER_SPACE uint32 = 0x00010108
32+
OID_GEN_RECEIVE_BUFFER_SPACE uint32 = 0x00010109
33+
OID_GEN_TRANSMIT_BLOCK_SIZE uint32 = 0x0001010A
34+
OID_GEN_RECEIVE_BLOCK_SIZE uint32 = 0x0001010B
35+
OID_GEN_VENDOR_ID uint32 = 0x0001010C
36+
OID_GEN_VENDOR_DESCRIPTION uint32 = 0x0001010D
37+
OID_GEN_CURRENT_PACKET_FILTER uint32 = 0x0001010E
38+
OID_GEN_MAXIMUM_TOTAL_SIZE uint32 = 0x00010111
39+
40+
OID_802_3_PERMANENT_ADDRESS uint32 = 0x01010101
41+
OID_802_3_CURRENT_ADDRESS uint32 = 0x01010102
42+
OID_802_3_MULTICAST_LIST uint32 = 0x01010103
43+
OID_802_3_MAXIMUM_LIST_SIZE uint32 = 0x01010104
44+
)
45+
46+
// RNDIS Packet Filters
47+
const (
48+
PacketTypeDirected uint32 = 0x00000001
49+
PacketTypeMulticast uint32 = 0x00000002
50+
PacketTypeAllMulticast uint32 = 0x00000004
51+
PacketTypeBroadcast uint32 = 0x00000008
52+
PacketTypeSourceRouting uint32 = 0x00000010
53+
PacketTypePromiscuous uint32 = 0x00000020
54+
PacketTypeSMT uint32 = 0x00000040
55+
PacketTypeAllLocal uint32 = 0x00000080
56+
PacketTypeAllFunctional uint32 = 0x00000100
57+
PacketTypeFunctional uint32 = 0x00000200
58+
PacketTypeGroup uint32 = 0x00000400
59+
)
60+
61+
// RNDIS Status Codes
62+
const (
63+
StatusSuccess uint32 = 0x00000000
64+
StatusFailure uint32 = 0xC0000001
65+
StatusNotSupported uint32 = 0xC00000BB
66+
StatusInvalidData uint32 = 0xC0010015
67+
StatusInvalidLength uint32 = 0xC0010014
68+
StatusResources uint32 = 0xC000009A
69+
)

0 commit comments

Comments
 (0)