Skip to content

Commit 0417fa9

Browse files
authored
Merge pull request #18 from CodePandaaAI/refactor-networking
Simplify text sharing with direct delivery and prepare preview 0.3.0
2 parents 8a70c7e + 1e47146 commit 0417fa9

35 files changed

Lines changed: 327 additions & 589 deletions

CHANGELOG.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [0.3.0] - 2026-08-27
10+
911
### Added
1012

1113
- Android-first manual rebuild with shared Compose Multiplatform Send and Receive UI.
@@ -14,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1416
- Current macOS/Linux DNS-SD/mDNS discovery and registration through JmDNS on eligible IPv4 and IPv6 LAN addresses.
1517
- Separate discovery and registration lifecycle states shared by Android, Desktop, the controller, and UI.
1618
- Stable per-install device identity and advertised dynamic HTTP/file-transfer ports.
17-
- Text offers, receiver Accept/Decline, text transfer, Copy, and Clear.
19+
- One-request direct text delivery with sender name, a 100,000-character limit, Copy, and Clear.
1820
- Android and Desktop multiple-file selection and metadata offers.
1921
- Raw TCP file transfer using one persistent connection per accepted batch.
2022
- Operation-bound file framing with operation ID, index, and size validation plus one final batch result containing receiver success and the completed-file count.
@@ -27,12 +29,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
2729
- Application-lifetime network startup and state-driven connection repair.
2830
- Enabled iOS device and Apple-silicon Simulator targets with native Bonjour discovery, document selection, clipboard, Files-visible storage, and streamed TCP transfer implementations.
2931
- Added an iOS-only GitHub Actions workflow for an unsigned Simulator app and optional development-signed iPhone IPA.
30-
- Prepared version `0.2.0` across Android, Desktop, and iOS; retained private Android release signing configuration and the permanent Windows MSI upgrade identity.
32+
- Prepared version `0.3.0` across Android, Desktop, and iOS; retained private Android release signing configuration and the permanent Windows MSI upgrade identity.
3133
- Public architecture, development, roadmap, security, privacy, and contribution documentation.
3234

3335
### Changed
3436

35-
- Made incoming transfer state the source of truth for offer type, operation identity, and acceptance phase; accepted text now shows a waiting state until its matching payload arrives.
37+
- Separated text from file operations: text now delivers directly while idle without an offer, decision, operation ID, waiting state, remote cancellation, or Cancel action.
38+
- Kept file offers, receiver decisions, operation IDs, cancellation, timeouts, progress, and raw TCP streaming unchanged in purpose.
3639
- Replaced the old generated sync implementation with a smaller, manually understood flow.
3740
- Separated Ktor HTTP offer/control messages from raw TCP file bytes.
3841
- Reused one TCP connection for the complete accepted multi-file batch instead of opening one connection per file.

README.md

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
### Android → Android
1717

1818
<img src="screenshots/hero-demo.gif" alt="Sync360 Android-to-Android text and file transfer demo" width="1080" />
19-
<sub>Nearby discovery, receiver approval, and direct Android-to-Android text/file transfer.</sub>
19+
<sub>Nearby discovery and direct Android-to-Android text/file transfer.</sub>
2020

2121
### Desktop → Android
2222

@@ -33,7 +33,7 @@ We have all done it: send a file to ourselves, wait for it to upload, open anoth
3333
Sync360 is for that nearby moment.
3434

3535
```text
36-
open app -> find nearby device -> choose what to send -> receiver approves -> send directly
36+
open app -> find nearby device -> choose text or files -> send directly
3737
```
3838

3939
The current app discovers other Sync360 devices on the same local network and transfers content directly between them. The transfer path does not use an account, cloud storage, or a Sync360 backend. It depends on the local network and the two devices involved.
@@ -50,8 +50,8 @@ In an initial Windows 11 Ethernet test, the native Windows DNS-SD backend discov
5050

5151
- Discover nearby Android devices with Android NSD/mDNS.
5252
- Advertise dynamic HTTP and file-transfer ports on the local network.
53-
- Send a text offer and let the receiver accept or decline it.
54-
- Transfer accepted text and copy it from the Receive screen.
53+
- Deliver text directly with one HTTP request when the receiver is idle.
54+
- Enforce a 100,000-character text limit and show the sender name with Copy and Clear actions.
5555
- Select images, videos, documents, and multiple files.
5656
- Show file metadata to the receiver before any file bytes are sent.
5757
- Stream file bytes directly over raw TCP without loading an entire file into memory.
@@ -92,13 +92,14 @@ The current progress UI tracks the exact bytes transferred across the accepted b
9292

9393
Sync360 uses two small networking paths with different jobs:
9494

95-
- **Ktor HTTP is the control plane.** It carries text/file offers, receiver decisions, file metadata, and text payloads.
95+
- **Ktor HTTP handles direct text delivery and the file control plane.** It carries text payloads, file offers, receiver decisions, and file metadata.
9696
- **Raw TCP is the file data plane.** It streams the actual file bytes directly between devices.
9797

9898
```mermaid
9999
flowchart LR
100100
A["Sender device"] -->|"Android NSD or platform Desktop DNS-SD"| B["Receiver device"]
101-
A -->|"Ktor: offer + decision + metadata"| B
101+
A -->|"Ktor: direct text delivery"| B
102+
A -->|"Ktor: file offer + decision"| B
102103
A -->|"Raw TCP: streamed file bytes"| B
103104
B -->|"Platform Downloads writer"| D["Downloads"]
104105
```
@@ -113,14 +114,12 @@ Android and Desktop start the shared network controller from their application e
113114
SendScreen
114115
-> SendScreenViewModel
115116
-> OutgoingRequestsController
116-
-> POST /sync360/text/offer
117-
-> receiver Accept/Decline
118-
-> accepted receiver waits for the matching text payload
119-
-> POST /sync360/text/transfer
120-
-> ReceiveScreen shows the text
117+
-> POST /sync360/text/deliver with sender name and text
118+
-> receiver atomically accepts only while idle
119+
-> ReceiveScreen shows the sender name and text
121120
```
122121

123-
The sender shares a preview and character count first. After acceptance, the receiver remains in a waiting-for-text state until the matching full text arrives. One operation ID ties the offer, accepted payload, and any explicit cancellation to the same sender operation.
122+
Text uses one request and has no offer, receiver decision, operation ID, waiting state, remote cancellation, or Cancel action. The UI, outgoing controller, and receiver reject text above 100,000 Kotlin `String.length` units. The receiver checks `Idle` and publishes the complete received text atomically under the incoming-operation mutex; otherwise it reports that it is busy.
124123

125124
### File path
126125

@@ -237,9 +236,9 @@ macOS/Linux:
237236
2. Connect both devices to the same Wi-Fi network or hotspot.
238237
3. Keep Sync360 open on both devices during the current foreground-only test flow.
239238
4. On the Send screen, wait for the other device to appear.
240-
5. Choose Text or Files, select the nearby device, and send an offer.
241-
6. Accept the offer on the receiving device.
242-
7. Received files will be written to the platform's Downloads folder.
239+
5. For text, enter the content and select the nearby device; idle receivers show it immediately.
240+
6. For files, select the files and nearby device, then accept the offer on the receiver.
241+
7. Accepted files will be written to the platform's Downloads folder.
243242

244243
Some routers enable client isolation and block local device-to-device traffic. If discovery or transfer does not work, try another trusted Wi-Fi network or a phone hotspot.
245244

@@ -251,7 +250,7 @@ Reload is available only after the current discovery window has stopped while se
251250

252251
Sync360 is **not secure for untrusted networks yet**.
253252

254-
The current implementation uses cleartext local HTTP and raw TCP. Operation IDs correlate offers, cancellation, accepted text, and file sockets for correctness, but they are not secret or authenticated. Sync360 does not yet authenticate the sender, encrypt content, or verify file integrity with a cryptographic hash. Receiver approval exists in the UI, but it is not a complete security boundary.
253+
The current implementation uses cleartext local HTTP and raw TCP. File operation IDs correlate offers, cancellation, and file sockets for correctness, but they are not secret or authenticated. Direct text delivery has no receiver approval. Sync360 does not yet authenticate the sender, encrypt content, or verify file integrity with a cryptographic hash. File receiver approval exists in the UI, but it is not a complete security boundary.
255254

256255
Use the current app only for development and testing on private networks you control. Please report security-sensitive findings according to [SECURITY.md](SECURITY.md), not in a public issue.
257256

@@ -278,7 +277,7 @@ Use the current app only for development and testing on private networks you con
278277
Sync360 is not trying to become a chat app, cloud-sync product, or permanent device manager. The product direction stays focused:
279278

280279
```text
281-
find nearby -> approve -> send directly
280+
find nearby -> send text or approve files -> transfer directly
282281
```
283282

284283
## Why the rebuild is intentionally small

androidApp/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ android {
3636
applicationId = "com.liftley.sync360"
3737
minSdk = libs.versions.android.minSdk.get().toInt()
3838
targetSdk = libs.versions.android.targetSdk.get().toInt()
39-
versionCode = 2
40-
versionName = "0.2.0"
39+
versionCode = 3
40+
versionName = "0.3.0"
4141
}
4242

4343
buildFeatures {

context.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Sync360 is an Android-first Kotlin Multiplatform / Compose Multiplatform app for nearby sharing over a local network.
44

55
```text
6-
open app -> discover nearby device -> choose content -> receiver approves -> send directly
6+
open app -> discover nearby device -> choose text or files -> send directly
77
```
88

99
The old AI-generated sync implementation was removed. The current app is being rebuilt manually so its maintainer can understand and own the complete discovery, request, transfer, and storage path.
@@ -16,7 +16,7 @@ The old AI-generated sync implementation was removed. The current app is being r
1616
- Windows discovery/registration through the operating system `dnsapi.dll` DNS-SD API on all interfaces.
1717
- Current macOS/Linux discovery/registration through JmDNS on eligible IPv4 and IPv6 LAN addresses.
1818
- Application-lifetime network startup with separate discovery and registration lifecycle states.
19-
- Ktor HTTP offers, receiver decisions, metadata, and text payloads.
19+
- Ktor HTTP direct text delivery plus file offers, receiver decisions, and metadata.
2020
- Raw TCP streaming for file bytes.
2121
- Multiple files sent sequentially over one accepted-batch connection.
2222
- Android file access through `ContentResolver` and Downloads writing through `MediaStore`.
@@ -45,8 +45,7 @@ Compose screen -> ViewModel -> controller/service -> common contract -> platform
4545
Ktor HTTP is the control plane:
4646

4747
```text
48-
POST /sync360/text/offer
49-
POST /sync360/text/transfer
48+
POST /sync360/text/deliver
5049
POST /sync360/file/offer
5150
POST /sync360/operation/cancel
5251
```
@@ -78,7 +77,7 @@ Current shared transfer constants use a 512 KiB payload buffer, 5-second connect
7877

7978
## Important limitations
8079

81-
Sync360 currently uses cleartext local HTTP and raw TCP. Operation IDs correlate protocol messages and file sockets but do not authenticate a peer. The app has receiver approval but no authentication, encryption, or checksum. The current target-SDK-37 Android build also lacks Android 17's required local-network runtime-permission flow. Windows receiving depends on Windows Firewall allowing the application. Use development builds only on private networks you control.
80+
Sync360 currently uses cleartext local HTTP and raw TCP. Direct text has no receiver approval or operation ID. File operation IDs correlate protocol messages and sockets but do not authenticate a peer. File offers require receiver approval, but the app has no authentication, encryption, or checksum. The current target-SDK-37 Android build also lacks Android 17's required local-network runtime-permission flow. Windows receiving depends on Windows Firewall allowing the application. Use development builds only on private networks you control.
8281

8382
For detailed and current information, read:
8483

desktopApp/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ compose.desktop {
3737
nativeDistributions {
3838
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
3939
packageName = "Sync360"
40-
packageVersion = "0.2.0"
40+
packageVersion = "0.3.0"
4141
appResourcesRootDir.set(
4242
project.layout.projectDirectory.dir("packaging/app-resources")
4343
)

docs/ARCHITECTURE.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@ app starts
1818
-> FileTransferReceiver opens an OS-assigned TCP port
1919
-> NetworkServices advertises both ports through DNS-SD/mDNS
2020
-> nearby Sync360 devices are resolved into NearbyDevice
21-
-> sender posts a text or file offer through Ktor HTTP
22-
-> receiver accepts or declines
23-
-> accepted text continues through HTTP
21+
-> sender delivers text directly or posts a file offer through Ktor HTTP
22+
-> idle receiver publishes the text, or the receiver accepts/declines the file offer
2423
-> accepted file bytes stream through one raw TCP connection
2524
-> platform DownloadsWriter saves the files
2625
```
@@ -74,8 +73,8 @@ ViewModels launch UI-facing work. They do not implement platform APIs or socket
7473
### Controllers
7574

7675
- `NetworkServicesController` starts the HTTP server, file receiver, and discovery/registration once for the application lifetime. It also coordinates timed discovery stop, discovery restart, and full connection repair.
77-
- `OutgoingRequestsController` creates offers, calls the Ktor client, and starts accepted file transfers.
78-
- `IncomingServerRequestsController` serializes Accept/Decline/Cancel races and uses `ClientServerState` as the source of truth for the active incoming operation. Text follows `TextOffer -> WaitingForText -> TextReceived`; files follow `FileOffer -> WaitingForFiles -> ReceivingFiles -> FilesReceived`. Active states retain their accepted request, so operation type, sender identity, operation ID, and acceptance phase are derived from state instead of duplicated in a second operation model.
76+
- `OutgoingRequestsController` validates and delivers text, creates file offers, and starts accepted file transfers.
77+
- `IncomingServerRequestsController` uses one operation mutex to atomically admit direct text only while idle and to serialize file Accept/Decline/Cancel races. Text follows `Idle -> TextReceived -> Idle`; files follow `Idle -> IncomingFileOffer -> WaitingForFiles -> ReceivingFiles -> FilesReceived`. File states retain their request, so sender identity, operation ID, and acceptance phase remain derived from state.
7978

8079
### Discovery
8180

@@ -99,16 +98,17 @@ The macOS/Linux JmDNS fallback starts on eligible IPv4 and IPv6 addresses from e
9998

10099
## Control plane: Ktor HTTP
101100

102-
Ktor carries offers, decisions, metadata, and text:
101+
Ktor carries direct text plus file offers, decisions, and metadata:
103102

104103
```text
105-
POST /sync360/text/offer
106-
POST /sync360/text/transfer
104+
POST /sync360/text/deliver
107105
POST /sync360/file/offer
108106
POST /sync360/operation/cancel
109107
```
110108

111-
An offer waits up to 50 seconds for the receiver's decision. After acceptance, the controller derives a 30-second payload-preparation timeout from `WaitingForText` or `WaitingForFiles`; leaving either state automatically cancels that timer. A random operation ID correlates the offer, accepted payload, explicit cancellation, and file connection. Cancellation succeeds only when both the operation ID and sender device ID match the active state. The timeouts remain fallbacks for crashes and lost network communication. The shared flow uses `FileOfferRequest` directly for the accepted metadata; file contents still remain in platform file readers and are not placed in the HTTP request.
109+
Text is delivered in one request containing the sender device name and text. It has no offer, decision, operation ID, waiting state, or cancellation route. Text above 100,000 Kotlin `String.length` units is rejected, and the receiver atomically checks `Idle` and publishes `TextReceived` under the operation mutex.
110+
111+
A file offer waits up to 50 seconds for the receiver's decision. After acceptance, the controller derives a 30-second payload-preparation timeout from `WaitingForFiles`. A random operation ID correlates the file offer, explicit cancellation, and file connection. Cancellation succeeds only when both the operation ID and sender device ID match the active file state. The timeouts remain fallbacks for crashes and lost network communication. The shared flow uses `FileOfferRequest` directly for the accepted metadata; file contents remain in platform file readers and are not placed in the HTTP request.
112112

113113
## File data plane: raw TCP
114114

docs/DEVELOPMENT.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Windows:
5656

5757
## Preparing public packages
5858

59-
The current package version is `0.2.0`.
59+
The current package version is `0.3.0`.
6060

6161
Android release APKs must use the maintainer's permanent private signing key. Copy `keystore.properties.example` to the ignored `keystore.properties` file and set:
6262

@@ -88,8 +88,8 @@ The Windows `upgradeUuid` must remain unchanged for the lifetime of Sync360, and
8888
1. Connect both devices to the same trusted Wi-Fi network or hotspot.
8989
2. Open Sync360 on both devices and keep it in the foreground during current testing.
9090
3. Wait for the other device to appear on the Send screen.
91-
4. Test a text offer: Accept, Decline, transfer, Copy, and Clear.
92-
5. Test one file, multiple files, and cancellation.
91+
4. Test direct text delivery while idle and busy, the 100,000/100,001 boundaries, sender name, Copy, and Clear.
92+
5. Test file Accept/Decline, one file, multiple files, and cancellation.
9393
6. Confirm completed files appear in Downloads.
9494
7. Resize the Desktop window and verify compact single-pane navigation and the wider 50/50 Send/Receive layout.
9595

@@ -121,7 +121,7 @@ Useful source locations:
121121
- `WindowsNetworkServices` — Windows DNS-SD registration, discovery, resolution, cancellation, and shared-state mapping.
122122
- `WindowsDnsSdApi` — focused JDK Foreign Function and Memory bindings for `dnsapi.dll`.
123123
- `JvmNetworkServices` — current macOS/Linux JmDNS registration, discovery, repair cleanup, and IPv4/IPv6 LAN-interface selection.
124-
- `Sync360HttpServer` / `Sync360HttpClient`offer and text routes.
124+
- `Sync360HttpServer` / `Sync360HttpClient`direct text delivery and file control routes.
125125
- `OutgoingRequestsController` / `IncomingServerRequestsController` — send/receive coordination.
126126
- platform `FileTransferSender`, `FileTransferReceiver`, and `DownloadsWriter` implementations — file bytes and storage.
127127

0 commit comments

Comments
 (0)