├── .gitignore ├── LICENSE ├── Makefile ├── README-ZH.md ├── README.md ├── USAGE-ZH.md ├── USAGE.md ├── app ├── .gitignore ├── .idea │ ├── .gitignore │ ├── .name │ ├── compiler.xml │ ├── gradle.xml │ ├── jarRepositories.xml │ ├── misc.xml │ └── vcs.xml ├── README.md ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── cn │ │ │ └── openp2p │ │ │ └── ExampleInstrumentedTest.kt │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── cn │ │ │ └── openp2p │ │ │ ├── BootReceiver.kt │ │ │ ├── OpenP2PService.kt │ │ │ ├── data │ │ │ ├── LoginDataSource.kt │ │ │ ├── LoginRepository.kt │ │ │ ├── Result.kt │ │ │ └── model │ │ │ │ └── LoggedInUser.kt │ │ │ ├── log.kt │ │ │ └── ui │ │ │ └── login │ │ │ ├── LoggedInUserView.kt │ │ │ ├── LoginActivity.kt │ │ │ ├── LoginFormState.kt │ │ │ ├── LoginResult.kt │ │ │ ├── LoginViewModel.kt │ │ │ └── LoginViewModelFactory.kt │ │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── icon.xml │ │ ├── layout │ │ └── activity_login.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── themes.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── openp2p.jks └── settings.gradle ├── cmd └── openp2p.go ├── config.json ├── core ├── common.go ├── common_test.go ├── config.go ├── config_test.go ├── daemon.go ├── errorcode.go ├── handlepush.go ├── holepunch.go ├── install.go ├── iptables.go ├── iptree.go ├── iptree_test.go ├── log.go ├── nat.go ├── openp2p.go ├── optun.go ├── optun_android.go ├── optun_darwin.go ├── optun_linux.go ├── optun_other.go ├── optun_windows.go ├── overlay.go ├── p2papp.go ├── p2pappkeys.go ├── p2pnetwork.go ├── p2ptunnel.go ├── p2ptunnel_test.go ├── ping.go ├── protocol.go ├── sdwan.go ├── speedlimiter.go ├── speedlimiter_test.go ├── udp.go ├── underlay.go ├── underlay_kcp.go ├── underlay_quic.go ├── underlay_tcp.go ├── underlay_tcp6.go ├── update.go ├── upnp.go ├── util_darwin.go ├── util_freebsd.go ├── util_linux.go ├── util_windows.go └── v4listener.go ├── doc ├── images │ ├── afterconnect.png │ ├── afterconnect_en.png │ ├── afterconnect_linux.png │ ├── appdetail.png │ ├── architecture.png │ ├── devices.png │ ├── devices_en.png │ ├── homeconnect.png │ ├── homeconnect_windows.png │ ├── install.png │ ├── install_en.png │ ├── mem.png │ ├── mstscconnect.png │ ├── mstscconnect_en.png │ ├── newapp.png │ ├── newapp_en.png │ ├── newappedit.png │ ├── newappedit_en.png │ ├── officeexecute_linux.png │ ├── officelisten.png │ ├── officelisten_2_linux.png │ ├── officelisten_linux.png │ ├── p2p-debug.png │ ├── p2pappok.png │ ├── p2pappok_en.png │ ├── prototype.png │ ├── register.png │ ├── register_en.png │ ├── release-debug.png │ ├── sshconnect.png │ ├── stillrun.png │ ├── stillrun_en.png │ ├── vs2022-remote-debug-attach.png │ ├── win10warn.png │ ├── win10warn_en.png │ └── winscpconnect.png ├── remote-debug-golang.md └── remote-debug-vscpp.md ├── docker ├── Dockerfile └── get-client.sh ├── example ├── dll │ └── dll.cpp └── echo │ ├── echo-client.go │ └── echo-server.go ├── go.mod └── lib └── openp2p.go /.gitignore: -------------------------------------------------------------------------------- 1 | __debug_bin 2 | __debug_bin.exe 3 | # .vscode 4 | test/ 5 | openp2p.exe* 6 | *.log* 7 | go.sum 8 | *.tar.gz 9 | *.zip 10 | *.exe 11 | config.json 12 | libs/ 13 | */app/.idea/ 14 | */app/release/ 15 | openp2p.app.jks 16 | openp2p.aar 17 | openp2p-sources.jar 18 | wintun/ 19 | wintun.dll 20 | .vscode/ 21 | app/.idea/ 22 | *_debug_bin* 23 | cmd/openp2p 24 | vendor/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 OpenP2P.cn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | export GOPROXY=https://goproxy.io,direct 3 | go mod tidy 4 | go build cmd/openp2p.go 5 | .PHONY: build 6 | 7 | .DEFAULT_GOAL := build 8 | -------------------------------------------------------------------------------- /README-ZH.md: -------------------------------------------------------------------------------- 1 | # [English](/README.md)|中文 2 | 网站: [openp2p.cn](https://openp2p.cn) 3 | ## OpenP2P是什么 4 | 它是一个开源、免费、轻量级的P2P共享网络。你的设备将组成一个私有P2P网络,里面的设备可以直接访问其它成员,或者通过其它成员转发数据间接访问。如果私有网络无法完成通信,将会到公有P2P网络寻找共享节点协助通信。 5 | 相比BT网络用来共享文件,OpenP2P网络用来共享带宽。 6 | 我们的目标是:充分利用带宽,利用共享节点转发数据,建设一个远程连接的通用基础设施。 7 | 8 | ## 为什么选择OpenP2P 9 | ### 1. 免费 10 | 完全免费,满足大部分用户的核心白票需求。不像其它类似的产品,OpenP2P不需要有公网IP的服务器,不需要花钱买服务。了解它原理即可理解为什么能做到免费。 11 | ### 2. 共享 12 | 你的设备会形成一个私有P2P网络,它们之间共享带宽,提供网络数据转发服务。 13 | 当你的私有P2P网络下没有可以提供转发服务的节点时,会尝试在公共P2P网络寻找转发节点。 14 | 默认会开启共享限速10mbps,只有你用户下提供了共享节点才能使用别人的共享节点。这非常公平,也是这个项目的初衷。 15 | 我们建议你在带宽足够的地方(比如办公室,家里的百兆光纤)加入共享网络。 16 | 如果你不想共享任何节点,或设置共享带宽,请查看[详细使用说明](/USAGE-ZH.md) 17 | ### 3. 安全 18 | 代码开源,P2P隧道使用TLS1.3+AES双重加密,共享节点临时授权使用TOTP一次性密码 19 | 20 | [查看详细](#安全性) 21 | ### 4. 轻量 22 | 文件大小2MB+,运行内存2MB+;它可以仅跑在应用层,或者配合wintun驱动使用组网功能 23 | ### 5. 跨平台 24 | 因为轻量,所以很容易支持各个平台。支持主流的操作系统:Windows,Linux,MacOS;和主流的cpu架构:386、amd64、arm、arm64、mipsle、mipsle64、mips、mips64、s390x、ppc64le 25 | ### 6. 高效 26 | P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络环境,无论NAT1-4(Cone或Symmetric),UDP或TCP打洞,UPNP,IPv6都支持。依靠Quic协议优秀的拥塞算法,能在糟糕的网络环境获得高带宽低延时。 27 | 28 | ### 7. 二次开发 29 | 基于OpenP2P只需数行代码,就能让原来只能局域网通信的程序,变成任何内网都能通信 30 | 31 | ## 快速入门 32 | 仅需简单4步就能用起来。 33 | 下面是一个远程办公例子:在家里连入办公室Windows电脑。 34 | (另外一个快速入门视频 ) 35 | ### 1.注册 36 | 前往 注册新用户,暂无需任何认证 37 | 38 | ![image](/doc/images/register.png) 39 | ### 2.安装 40 | 分别在本地和远程电脑下载后双击运行,一键安装 41 | 42 | ![image](/doc/images/install.png) 43 | 44 | Windows默认会阻止没有花钱买它家证书签名过的程序,选择“仍要运行”即可。 45 | 46 | ![image](/doc/images/win10warn.png) 47 | 48 | ![image](/doc/images/stillrun.png) 49 | ### 3.新建P2P应用 50 | 51 | ![image](/doc/images/devices.png) 52 | 53 | ![image](/doc/images/newapp.png) 54 | 55 | ![image](/doc/images/newappedit.png) 56 | 57 | ### 4.使用P2P应用 58 | 在“MyHomePC”设备上能看到刚才创建的P2P应用,连接下图显示的“本地监听端口”即可。 59 | 60 | ![image](/doc/images/p2pappok.png) 61 | 62 | 在家里Windows电脑,按Win+R输入mstsc打开远程桌面,输入127.0.0.1:23389 /admin 63 | 64 | ![image](/doc/images/mstscconnect.png) 65 | 66 | ![image](/doc/images/afterconnect.png) 67 | 68 | ## 详细使用说明 69 | [这里](/USAGE-ZH.md)介绍如何手动运行 70 | 71 | ## 典型应用场景 72 | 特别适合大流量的内网访问 73 | >* 远程办公: Windows MSTSC、VNC等远程桌面,SSH,内网各种ERP系统 74 | >* 远程访问内网ERP系统 75 | >* 远程访问NAS: 管理大量视频、图片 76 | >* 远程监控摄像头 77 | >* 远程刷机 78 | >* 远程数据备份 79 | --- 80 | ## 概要设计 81 | ### 原型 82 | ![image](/doc/images/prototype.png) 83 | ### 客户端架构 84 | ![image](/doc/images/architecture.png) 85 | ### P2PApp 86 | 它是项目里最重要的概念,一个P2PApp就是把远程的一个服务(mstsc/ssh等)通过P2P网络映射到本地监听。二次开发或者我们提供的Restful API,主要工作就是管理P2PApp 87 | ![image](/doc/images/appdetail.png) 88 | ## 安全性 89 | 加入OpenP2P共享网络的节点,只能凭授权访问。共享节点只会中转数据,别人无法访问内网任何资源。 90 | ### 1. TLS1.3+AES 91 | 两个节点间通信数据走业界最安全的TLS1.3通道。通信内容还会使用AES加密,双重安全,密钥是通过服务端作换。有效阻止中间人攻击 92 | ### 2. 共享的中转节点是否会获得我的数据 93 | 没错,中转节点天然就是一个中间人,所以才加上AES加密通信内容保证安全。中转节点是无法获取明文的 94 | 95 | ### 3. 中转节点是如何校验权限的 96 | 服务端有个调度模型,根据带宽、ping值、稳定性、服务时长,尽可能地使共享节点均匀地提供服务。连接共享节点使用TOTP密码,hmac-sha256算法校验,它是一次性密码,和我们平时使用的手机验证码或银行密码器一样的原理。 97 | 98 | ## 编译 99 | go version 1.20 only (支持win7) 100 | cd到代码根目录,执行 101 | ``` 102 | make 103 | ``` 104 | 手动编译特定系统和架构 105 | All GOOS values: 106 | ``` 107 | "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "js", "linux", "nacl", "netbsd", "openbsd", "plan9", "solaris", "windows", "zos" 108 | ``` 109 | All GOARCH values: 110 | ``` 111 | "386", "amd64", "amd64p32", "arm", "arm64", "arm64be", "armbe", "loong64", "mips", "mips64", "mips64le", "mips64p32", "mips64p32le", "mipsle", "ppc", "ppc64", "ppc64le", "riscv", "riscv64", "s390", "s390x", "sparc", "sparc64", "wasm" 112 | ``` 113 | 114 | 比如linux+amd64 115 | ``` 116 | export GOPROXY=https://goproxy.io,direct 117 | go mod tidy 118 | CGO_ENABLED=0 env GOOS=linux GOARCH=amd64 go build -o openp2p --ldflags '-s -w ' -gcflags '-l' -p 8 -installsuffix cgo ./cmd 119 | ``` 120 | 121 | ## RoadMap 122 | 近期计划: 123 | 1. ~~支持IPv6~~(100%) 124 | 2. ~~支持随系统自动启动,安装成系统服务~~(100%) 125 | 3. ~~提供一些免费服务器给特别差的网络,如广电网络~~(100%) 126 | 4. ~~建立网站,用户可以在网站管理所有P2PApp和设备。查看设备在线状态,升级,增删查改重启P2PApp等~~(100%) 127 | 5. 建立公众号,用户可在微信公众号管理所有P2PApp和设备 128 | 6. 客户端提供WebUI 129 | 7. ~~支持自有服务器,开源服务器程序~~(100%) 130 | 8. 共享节点调度模型优化,对不同的运营商优化 131 | 9. ~~方便二次开发,提供API和lib~~(100%) 132 | 10. ~~应用层支持UDP协议,实现很简单,但UDP应用较少暂不急~~(100%) 133 | 11. ~~底层通信支持KCP协议,目前仅支持Quic;KCP专门对延时优化,被游戏加速器广泛使用,可以牺牲一定的带宽降低延时~~(100%) 134 | 12. ~~支持Android系统,让旧手机焕发青春变成移动网关~~(100%) 135 | 13. ~~支持Windows网上邻居共享文件~~(100%) 136 | 14. ~~内网直连优化~~(100%) 137 | 15. ~~支持UPNP~~(100%) 138 | 16. ~~支持Android~~(100%) 139 | 17. 支持IOS 140 | 141 | 远期计划: 142 | 1. 利用区块链技术去中心化,让共享设备的用户有收益,从而促进更多用户共享,达到正向闭环。 143 | 2. 企业级支持,可以更好地管理大量设备,和更安全更细的权限控制 144 | 145 | ## 参与贡献 146 | TODO或ISSUE里如果有你擅长的领域,或者你有特别好的主意,可以加入OpenP2P项目,贡献你的代码。待项目茁壮成长后,你们就是知名开源项目的主要代码贡献者,岂不快哉。 147 | 148 | ## 技术交流 149 | QQ群:16947733 150 | 邮箱:openp2p.cn@gmail.com tenderiron@139.com 151 | 152 | ## 免责声明 153 | 本项目开源供大家学习和免费使用,禁止用于非法用途,任何不当使用本项目或意外造成的损失,本项目及相关人员不会承担任何责任。 154 | -------------------------------------------------------------------------------- /USAGE-ZH.md: -------------------------------------------------------------------------------- 1 | # 手动运行说明 2 | 大部分情况通过 操作即可。有些情况需要手动运行 3 | > :warning: 本文所有命令, Windows环境使用"openp2p.exe", Linux环境使用"./openp2p" 4 | 5 | 6 | ## 安装和监听 7 | ``` 8 | ./openp2p install -node OFFICEPC1 -token TOKEN 9 | 或 10 | ./openp2p -d -node OFFICEPC1 -token TOKEN 11 | # 注意Windows系统把“./openp2p” 换成“openp2p.exe” 12 | ``` 13 | >* install: 安装模式【推荐】,会安装成系统服务,这样它就能随系统自动启动 14 | >* -d: daemon模式。发现worker进程意外退出就会自动启动新的worker进程 15 | >* -node: 独一无二的节点名字,唯一标识 16 | >* -token: 在“我的”里面找到 17 | >* -sharebandwidth: 作为共享节点时提供带宽,默认10mbps. 如果是光纤大带宽,设置越大效果越好. 0表示不共享,该节点只在私有的P2P网络使用。不加入共享的P2P网络,这样也意味着无法使用别人的共享节点 18 | >* -loglevel: 需要查看更多调试日志,设置0;默认是1 19 | 20 | ### 在docker容器里运行openp2p 21 | 我们暂时还没提供官方docker镜像,你可以在随便一个容器里运行 22 | ``` 23 | nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & 24 | #这里由于一般的镜像都精简过,install系统服务会失败,所以使用直接daemon模式后台运行 25 | ``` 26 | ## 连接 27 | ``` 28 | ./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 29 | 使用配置文件,建立多个P2PApp 30 | ./openp2p -d 31 | ``` 32 | >* -appname: 这个P2P应用名字 33 | >* -peernode: 目标节点名字 34 | >* -dstip: 目标服务地址,默认本机127.0.0.1 35 | >* -dstport: 目标服务端口,常见的如windows远程桌面3389,Linux ssh 22 36 | >* -protocol: 目标服务协议 tcp、udp 37 | 38 | ## 配置文件 39 | 一般保存在当前目录,安装模式下会保存到 `C:\Program Files\OpenP2P\config.json` 或 `/usr/local/openp2p/config.json` 40 | 希望修改参数,或者配置多个P2PApp可手动修改配置文件 41 | 42 | 配置实例 43 | ``` 44 | { 45 | "network": { 46 | "Node": "YOUR-NODE-NAME", 47 | "Token": "TOKEN", 48 | "ShareBandwidth": 0, 49 | "ServerHost": "api.openp2p.cn", 50 | "ServerPort": 27183, 51 | "UDPPort1": 27182, 52 | "UDPPort2": 27183 53 | }, 54 | "apps": [ 55 | { 56 | "AppName": "OfficeWindowsPC", 57 | "Protocol": "tcp", 58 | "SrcPort": 23389, 59 | "PeerNode": "OFFICEPC1", 60 | "DstPort": 3389, 61 | "DstHost": "localhost", 62 | }, 63 | { 64 | "AppName": "OfficeServerSSH", 65 | "Protocol": "tcp", 66 | "SrcPort": 22, 67 | "PeerNode": "OFFICEPC1", 68 | "DstPort": 22, 69 | "DstHost": "192.168.1.5", 70 | } 71 | ] 72 | } 73 | ``` 74 | 75 | ## 升级客户端 76 | ``` 77 | # update local client 78 | ./openp2p update 79 | # update remote client 80 | curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' 81 | ``` 82 | 83 | Windows系统需要设置防火墙放行本程序,程序会自动设置,如果设置失败会影响连接功能。 84 | Linux系统(Ubuntu和CentOS7)的防火墙默认配置均不会有影响,如果不行可尝试关闭防火墙 85 | ``` 86 | systemctl stop firewalld.service 87 | systemctl start firewalld.service 88 | firewall-cmd --state 89 | ``` 90 | ## 停止 91 | TODO: windows linux macos 92 | ## 卸载 93 | ``` 94 | ./openp2p uninstall 95 | # 已安装时 96 | # windows 97 | C:\Program Files\OpenP2P\openp2p.exe uninstall 98 | # linux,macos 99 | sudo /usr/local/openp2p/openp2p uninstall 100 | ``` 101 | 102 | ## Docker运行 103 | ``` 104 | # 把YOUR-TOKEN和YOUR-NODE-NAME替换成自己的 105 | docker run -d --restart=always --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest 106 | OR 107 | docker run -d --restart=always --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME 108 | ``` 109 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Parameters details 4 | In most cases, you can operate it through . In some cases it is necessary to run manually 5 | > :warning: all commands in this doc, Windows env uses "openp2p.exe", Linux env uses "./openp2p" 6 | 7 | 8 | ## Install and Listen 9 | ``` 10 | ./openp2p install -node OFFICEPC1 -token TOKEN 11 | Or 12 | ./openp2p -d -node OFFICEPC1 -token TOKEN 13 | 14 | ``` 15 | >* install: [recommand] will install as system service. So it will autorun when system booting. 16 | >* -d: daemon mode run once. When the worker process is found to exit unexpectedly, a new worker process will be automatically started 17 | >* -node: Unique node name, unique identification 18 | >* -token: See "Profile" 19 | >* -sharebandwidth: Provides bandwidth when used as a shared node, the default is 10mbps. If it is a large bandwidth of optical fiber, the larger the setting, the better the effect. 0 means not shared, the node is only used in a private P2P network. Do not join the shared P2P network, which also means that you CAN NOT use other people’s shared nodes 20 | >* -loglevel: Need to view more debug logs, set 0; the default is 1 21 | 22 | ### Run in Docker container 23 | We don't provide official docker image yet, you can run it in any container 24 | ``` 25 | nohup ./openp2p -d -node OFFICEPC1 -token TOKEN & 26 | # Since many docker images have been simplified, the install system service will fail, so the daemon mode is used to run in the background 27 | ``` 28 | 29 | ## Connect 30 | ``` 31 | ./openp2p -d -node HOMEPC123 -token TOKEN -appname OfficeWindowsRemote -peernode OFFICEPC1 -dstip 127.0.0.1 -dstport 3389 -srcport 23389 32 | Create multiple P2PApp by config file 33 | ./openp2p -d 34 | ``` 35 | >* -appname: This P2PApp name 36 | >* -peernode: Target node name 37 | >* -dstip: Target service address, default local 127.0.0.1 38 | >* -dstport: Target service port, such as windows remote desktop 3389, Linux ssh 22 39 | >* -protocol: Target service protocol tcp, udp 40 | 41 | ## Config file 42 | Generally saved in the current directory, in installation mode it will be saved to `C:\Program Files\OpenP2P\config.json` or `/usr/local/openp2p/config.json` 43 | If you want to modify the parameters, or configure multiple P2PApps, you can manually modify the configuration file 44 | 45 | Configuration example 46 | ``` 47 | { 48 | "network": { 49 | "Node": "YOUR-NODE-NAME", 50 | "Token": "TOKEN", 51 | "ShareBandwidth": 0, 52 | "ServerHost": "api.openp2p.cn", 53 | "ServerPort": 27183, 54 | "UDPPort1": 27182, 55 | "UDPPort2": 27183 56 | }, 57 | "apps": [ 58 | { 59 | "AppName": "OfficeWindowsPC", 60 | "Protocol": "tcp", 61 | "SrcPort": 23389, 62 | "PeerNode": "OFFICEPC1", 63 | "DstPort": 3389, 64 | "DstHost": "localhost", 65 | }, 66 | { 67 | "AppName": "OfficeServerSSH", 68 | "Protocol": "tcp", 69 | "SrcPort": 22, 70 | "PeerNode": "OFFICEPC1", 71 | "DstPort": 22, 72 | "DstHost": "192.168.1.5", 73 | } 74 | ] 75 | } 76 | ``` 77 | ## Client update 78 | ``` 79 | # update local client 80 | ./openp2p update 81 | # update remote client 82 | curl --insecure 'https://api.openp2p.cn:27183/api/v1/device/YOUR-NODE-NAME/update?user=&password=' 83 | ``` 84 | 85 | Windows system needs to set up firewall for this program, the program will automatically set the firewall, if the setting fails, the UDP punching will be affected. 86 | The default firewall configuration of Linux system (Ubuntu and CentOS7) will not have any effect, if not, you can try to turn off the firewall 87 | ``` 88 | systemctl stop firewalld.service 89 | systemctl start firewalld.service 90 | firewall-cmd --state 91 | ``` 92 | 93 | ## Uninstall 94 | ``` 95 | ./openp2p uninstall 96 | # when already installed 97 | # windows 98 | C:\Program Files\OpenP2P\openp2p.exe uninstall 99 | # linux,macos 100 | sudo /usr/local/openp2p/openp2p uninstall 101 | ``` 102 | 103 | ## Run with Docker 104 | ``` 105 | # Replace YOUR-TOKEN and YOUR-NODE-NAME with yours 106 | docker run -d --net host --name openp2p-client -e OPENP2P_TOKEN=YOUR-TOKEN -e OPENP2P_NODE=YOUR-NODE-NAME openp2pcn/openp2p-client:latest 107 | OR 108 | docker run -d --net host --name openp2p-client openp2pcn/openp2p-client:latest -token YOUR-TOKEN -node YOUR-NODE-NAME 109 | ``` -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/.idea/.name: -------------------------------------------------------------------------------- 1 | OpenP2P -------------------------------------------------------------------------------- /app/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /app/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /app/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | ## Build 2 | depends on openjdk 11, gradle 8.1.3, ndk 21 3 | ``` 4 | 5 | # latest version not support go1.20 6 | go install golang.org/x/mobile/cmd/gomobile@7c4916698cc93475ebfea76748ee0faba2deb2a5 7 | gomobile init 8 | go get -v golang.org/x/mobile/bind@7c4916698cc93475ebfea76748ee0faba2deb2a5 9 | cd core 10 | gomobile bind -target android -v 11 | if [[ $? -ne 0 ]]; then 12 | echo "build error" 13 | exit 9 14 | fi 15 | echo "build ok" 16 | cp openp2p.aar openp2p-sources.jar ../app/app/libs 17 | echo "copy to APP libs" 18 | 19 | edit app/app/build.gradle 20 | ``` 21 | signingConfigs { 22 | release { 23 | storeFile file('YOUR-JKS-PATH') 24 | storePassword 'YOUR-PASSWORD' 25 | keyAlias 'openp2p.keys' 26 | keyPassword 'YOUR-PASSWORD' 27 | } 28 | } 29 | ``` 30 | cd ../app 31 | ./gradlew build 32 | 33 | ``` -------------------------------------------------------------------------------- /app/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | } 5 | 6 | android { 7 | signingConfigs { 8 | release { 9 | storeFile file('C:\\work\\src\\openp2p-client\\app\\openp2p.jks') 10 | storePassword 'YOUR-PASSWORD' 11 | keyAlias 'openp2p.keys' 12 | keyPassword 'YOUR-PASSWORD' 13 | } 14 | } 15 | compileSdkVersion 31 16 | buildToolsVersion "30.0.3" 17 | 18 | defaultConfig { 19 | applicationId "cn.openp2p" 20 | minSdkVersion 16 21 | targetSdkVersion 31 22 | versionCode 1 23 | versionName "2718281828" 24 | 25 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 26 | namespace "cn.openp2p" 27 | } 28 | 29 | buildTypes { 30 | release { 31 | minifyEnabled true 32 | shrinkResources true 33 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 34 | signingConfig signingConfigs.release 35 | } 36 | } 37 | compileOptions { 38 | sourceCompatibility JavaVersion.VERSION_1_8 39 | targetCompatibility JavaVersion.VERSION_1_8 40 | } 41 | kotlinOptions { 42 | jvmTarget = '1.8' 43 | } 44 | buildFeatures { 45 | viewBinding true 46 | } 47 | } 48 | 49 | dependencies { 50 | implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"]) 51 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 52 | implementation 'androidx.core:core-ktx:1.3.1' 53 | implementation 'androidx.appcompat:appcompat:1.2.0' 54 | implementation 'com.google.android.material:material:1.2.1' 55 | implementation 'androidx.annotation:annotation:1.1.0' 56 | implementation 'androidx.constraintlayout:constraintlayout:2.0.1' 57 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' 58 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' 59 | testImplementation 'junit:junit:4.+' 60 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 61 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 62 | implementation files('libs\\openp2p-sources.jar') 63 | } -------------------------------------------------------------------------------- /app/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /app/app/src/androidTest/java/cn/openp2p/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("cn.openp2p", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/BootReceiver.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.net.VpnService 7 | import android.os.Build 8 | import android.os.Bundle 9 | import android.util.Log 10 | import cn.openp2p.ui.login.LoginActivity 11 | class BootReceiver : BroadcastReceiver() { 12 | override fun onReceive(context: Context, intent: Intent) { 13 | // Logger.log("pp onReceive "+intent.action.toString()) 14 | Log.i("onReceive","start "+intent.action.toString()) 15 | // if (Intent.ACTION_BOOT_COMPLETED == intent.action) { 16 | // Log.i("onReceive","match "+intent.action.toString()) 17 | // VpnService.prepare(context) 18 | // val intent = Intent(context, OpenP2PService::class.java) 19 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 20 | // context.startForegroundService(intent) 21 | // } else { 22 | // context.startService(intent) 23 | // } 24 | // } 25 | Log.i("onReceive","end "+intent.action.toString()) 26 | } 27 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/data/LoginDataSource.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.data 2 | 3 | import cn.openp2p.data.model.LoggedInUser 4 | import java.io.IOException 5 | 6 | /** 7 | * Class that handles authentication w/ login credentials and retrieves user information. 8 | */ 9 | class LoginDataSource { 10 | 11 | fun login(username: String, password: String): Result { 12 | try { 13 | // TODO: handle loggedInUser authentication 14 | val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe") 15 | return Result.Success(fakeUser) 16 | } catch (e: Throwable) { 17 | return Result.Error(IOException("Error logging in", e)) 18 | } 19 | } 20 | 21 | fun logout() { 22 | // TODO: revoke authentication 23 | } 24 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/data/LoginRepository.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.data 2 | 3 | import cn.openp2p.data.model.LoggedInUser 4 | 5 | /** 6 | * Class that requests authentication and user information from the remote data source and 7 | * maintains an in-memory cache of login status and user credentials information. 8 | */ 9 | 10 | class LoginRepository(val dataSource: LoginDataSource) { 11 | 12 | // in-memory cache of the loggedInUser object 13 | var user: LoggedInUser? = null 14 | private set 15 | 16 | val isLoggedIn: Boolean 17 | get() = user != null 18 | 19 | init { 20 | // If user credentials will be cached in local storage, it is recommended it be encrypted 21 | // @see https://developer.android.com/training/articles/keystore 22 | user = null 23 | } 24 | 25 | fun logout() { 26 | user = null 27 | dataSource.logout() 28 | } 29 | 30 | fun login(username: String, password: String): Result { 31 | // handle login 32 | val result = dataSource.login(username, password) 33 | 34 | if (result is Result.Success) { 35 | setLoggedInUser(result.data) 36 | } 37 | 38 | return result 39 | } 40 | 41 | private fun setLoggedInUser(loggedInUser: LoggedInUser) { 42 | this.user = loggedInUser 43 | // If user credentials will be cached in local storage, it is recommended it be encrypted 44 | // @see https://developer.android.com/training/articles/keystore 45 | } 46 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/data/Result.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.data 2 | 3 | /** 4 | * A generic class that holds a value with its loading status. 5 | * @param 6 | */ 7 | sealed class Result { 8 | 9 | data class Success(val data: T) : Result() 10 | data class Error(val exception: Exception) : Result() 11 | 12 | override fun toString(): String { 13 | return when (this) { 14 | is Success<*> -> "Success[data=$data]" 15 | is Error -> "Error[exception=$exception]" 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/data/model/LoggedInUser.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.data.model 2 | 3 | /** 4 | * Data class that captures user information for logged in users retrieved from LoginRepository 5 | */ 6 | data class LoggedInUser( 7 | val userId: String, 8 | val displayName: String 9 | ) -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/log.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p 2 | import android.content.* 3 | import java.io.File 4 | import java.io.FileWriter 5 | import java.text.SimpleDateFormat 6 | import java.util.* 7 | import android.app.* 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.graphics.Color 11 | import java.io.IOException 12 | import android.net.VpnService 13 | import android.os.Binder 14 | import android.os.Build 15 | import android.os.IBinder 16 | import android.os.ParcelFileDescriptor 17 | import android.util.Log 18 | import androidx.annotation.RequiresApi 19 | import androidx.core.app.NotificationCompat 20 | import cn.openp2p.ui.login.LoginActivity 21 | import kotlinx.coroutines.GlobalScope 22 | import kotlinx.coroutines.launch 23 | import openp2p.Openp2p 24 | import java.io.FileInputStream 25 | import java.io.FileOutputStream 26 | import java.nio.ByteBuffer 27 | import kotlinx.coroutines.* 28 | import org.json.JSONObject 29 | 30 | object Logger { 31 | private val logFile: File = File("app.log") 32 | 33 | fun log(message: String) { 34 | val timestamp = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(Date()) 35 | val logMessage = "$timestamp: $message\n" 36 | 37 | try { 38 | val fileWriter = FileWriter(logFile, true) 39 | fileWriter.append(logMessage) 40 | fileWriter.close() 41 | } catch (e: Exception) { 42 | e.printStackTrace() 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoggedInUserView.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | /** 4 | * User details post authentication that is exposed to the UI 5 | */ 6 | data class LoggedInUserView( 7 | val displayName: String 8 | //... other data fields that may be accessible to the UI 9 | ) -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Activity 5 | import android.app.ActivityManager 6 | import android.app.Notification 7 | import android.app.PendingIntent 8 | import android.content.BroadcastReceiver 9 | import android.content.ComponentName 10 | import android.content.Context 11 | import android.content.Intent 12 | import android.content.ServiceConnection 13 | import android.net.Uri 14 | import android.net.VpnService 15 | import android.os.Build 16 | import android.os.Bundle 17 | import android.os.IBinder 18 | import android.text.Editable 19 | import android.text.TextWatcher 20 | import android.util.Log 21 | import android.view.View 22 | import android.widget.EditText 23 | import android.widget.Toast 24 | import androidx.annotation.RequiresApi 25 | import androidx.annotation.StringRes 26 | import androidx.appcompat.app.AppCompatActivity 27 | import androidx.lifecycle.Observer 28 | import androidx.lifecycle.ViewModelProvider 29 | import cn.openp2p.Logger 30 | import cn.openp2p.OpenP2PService 31 | import cn.openp2p.R 32 | import cn.openp2p.databinding.ActivityLoginBinding 33 | import openp2p.Openp2p 34 | import kotlin.concurrent.thread 35 | import kotlin.system.exitProcess 36 | 37 | 38 | class LoginActivity : AppCompatActivity() { 39 | companion object { 40 | private val LOG_TAG = LoginActivity::class.simpleName 41 | } 42 | 43 | private val connection = object : ServiceConnection { 44 | override fun onServiceConnected(className: ComponentName, service: IBinder) { 45 | val binder = service as OpenP2PService.LocalBinder 46 | mService = binder.getService() 47 | } 48 | 49 | override fun onServiceDisconnected(className: ComponentName) { 50 | 51 | } 52 | } 53 | private lateinit var loginViewModel: LoginViewModel 54 | private lateinit var binding: ActivityLoginBinding 55 | private lateinit var mService: OpenP2PService 56 | @RequiresApi(Build.VERSION_CODES.O) 57 | override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 58 | super.onActivityResult(requestCode, resultCode, data) 59 | if (requestCode == 0 && resultCode == Activity.RESULT_OK) { 60 | startService(Intent(this, OpenP2PService::class.java)) 61 | } 62 | } 63 | 64 | @RequiresApi(Build.VERSION_CODES.O) 65 | override fun onCreate(savedInstanceState: Bundle?) { 66 | super.onCreate(savedInstanceState) 67 | 68 | binding = ActivityLoginBinding.inflate(layoutInflater) 69 | setContentView(binding.root) 70 | 71 | val token = binding.token 72 | val login = binding.login 73 | val onlineState = binding.onlineState 74 | val openp2pLog = binding.openp2pLog 75 | val profile = binding.profile 76 | val loading = binding.loading 77 | 78 | loginViewModel = ViewModelProvider(this, LoginViewModelFactory()) 79 | .get(LoginViewModel::class.java) 80 | 81 | loginViewModel.loginFormState.observe(this@LoginActivity, Observer { 82 | val loginState = it ?: return@Observer 83 | 84 | // disable login button unless both username / password is valid 85 | login.isEnabled = loginState.isDataValid 86 | 87 | if (loginState.passwordError != null) { 88 | token.error = getString(loginState.passwordError) 89 | } 90 | }) 91 | openp2pLog.setText(R.string.phone_setting) 92 | val intent = VpnService.prepare(this) 93 | if (intent != null) 94 | { 95 | Log.i("openp2p", "VpnService.prepare need permission"); 96 | startActivityForResult(intent, 0) 97 | } 98 | else { 99 | Log.i("openp2p", "VpnService.prepare ready"); 100 | onActivityResult(0, Activity.RESULT_OK, null) 101 | } 102 | 103 | 104 | profile.setOnClickListener { 105 | val url = "https://console.openp2p.cn/profile" 106 | val i = Intent(Intent.ACTION_VIEW) 107 | i.data = Uri.parse(url) 108 | startActivity(i) 109 | } 110 | token.apply { 111 | afterTextChanged { 112 | loginViewModel.loginDataChanged( 113 | "username.text.toString()", 114 | token.text.toString() 115 | ) 116 | } 117 | 118 | openp2pLog.setText(R.string.phone_setting) 119 | 120 | login.setOnClickListener { 121 | if (login.text.toString()=="退出"){ 122 | // val intent = Intent(this@LoginActivity, OpenP2PService::class.java) 123 | // stopService(intent) 124 | Log.i(LOG_TAG, "quit") 125 | mService.stop() 126 | 127 | val intent = Intent(this@LoginActivity, OpenP2PService::class.java) 128 | stopService(intent) 129 | // 解绑服务 130 | unbindService(connection) 131 | 132 | // 结束当前 Activity 133 | finish() // 或者使用 finishAffinity() 来结束整个应用程序 134 | exitAPP() 135 | // finishAffinity() 136 | 137 | } 138 | login.setText("退出") 139 | Log.i(LOG_TAG, "start") 140 | val intent = Intent(this@LoginActivity, OpenP2PService::class.java) 141 | intent.putExtra("token", token.text.toString()) 142 | bindService(intent, connection, Context.BIND_AUTO_CREATE) 143 | startService(intent) 144 | thread { 145 | do { 146 | Thread.sleep(1000) 147 | if (!::mService.isInitialized) continue 148 | val isConnect = mService.isConnected() 149 | // Log.i(LOG_TAG, "mService.isConnected() = " + isConnect.toString()) 150 | runOnUiThread { 151 | if (isConnect) { 152 | onlineState.setText("在线") 153 | } else { 154 | onlineState.setText("正在登录") 155 | } 156 | } 157 | } while (true) 158 | } 159 | 160 | } 161 | val tokenText = Openp2p.getToken(getExternalFilesDir(null).toString()) 162 | token.setText(tokenText.toString()) 163 | // Check token length and automatically click login if length > 10 164 | if (tokenText.length > 10) { 165 | // Logger.log("performClick ") 166 | login.performClick() 167 | } 168 | } 169 | } 170 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP) 171 | @SuppressLint("ServiceCast") 172 | fun exitAPP() { 173 | val activityManager = 174 | applicationContext?.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager 175 | val appTaskList = activityManager.appTasks 176 | 177 | for (i in appTaskList.indices) { 178 | appTaskList[i].finishAndRemoveTask() 179 | } 180 | exitProcess(0) 181 | } 182 | 183 | private fun updateUiWithUser(model: LoggedInUserView) { 184 | val welcome = getString(R.string.welcome) 185 | val displayName = model.displayName 186 | // TODO : initiate successful logged in experience 187 | Toast.makeText( 188 | applicationContext, 189 | "$welcome $displayName", 190 | Toast.LENGTH_LONG 191 | ).show() 192 | } 193 | 194 | private fun showLoginFailed(@StringRes errorString: Int) { 195 | Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show() 196 | } 197 | } 198 | 199 | /** 200 | * Extension function to simplify setting an afterTextChanged action to EditText components. 201 | */ 202 | fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) { 203 | this.addTextChangedListener(object : TextWatcher { 204 | override fun afterTextChanged(editable: Editable?) { 205 | afterTextChanged.invoke(editable.toString()) 206 | } 207 | 208 | override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} 209 | 210 | override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {} 211 | }) 212 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoginFormState.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | /** 4 | * Data validation state of the login form. 5 | */ 6 | data class LoginFormState( 7 | val usernameError: Int? = null, 8 | val passwordError: Int? = null, 9 | val isDataValid: Boolean = false 10 | ) -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoginResult.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | /** 4 | * Authentication result : success (user details) or error message. 5 | */ 6 | data class LoginResult( 7 | val success: LoggedInUserView? = null, 8 | val error: Int? = null 9 | ) -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoginViewModel.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import android.util.Patterns 7 | import cn.openp2p.data.LoginRepository 8 | import cn.openp2p.data.Result 9 | 10 | import cn.openp2p.R 11 | 12 | class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() { 13 | 14 | private val _loginForm = MutableLiveData() 15 | val loginFormState: LiveData = _loginForm 16 | 17 | private val _loginResult = MutableLiveData() 18 | val loginResult: LiveData = _loginResult 19 | 20 | fun login(username: String, password: String) { 21 | // can be launched in a separate asynchronous job 22 | val result = loginRepository.login(username, password) 23 | 24 | if (result is Result.Success) { 25 | _loginResult.value = 26 | LoginResult(success = LoggedInUserView(displayName = result.data.displayName)) 27 | } else { 28 | _loginResult.value = LoginResult(error = R.string.login_failed) 29 | } 30 | } 31 | 32 | fun loginDataChanged(username: String, password: String) { 33 | // if (!isUserNameValid(username)) { 34 | // _loginForm.value = LoginFormState(usernameError = R.string.invalid_username) 35 | // } else 36 | if (!isPasswordValid(password)) { 37 | _loginForm.value = LoginFormState(passwordError = R.string.invalid_password) 38 | } else { 39 | _loginForm.value = LoginFormState(isDataValid = true) 40 | } 41 | } 42 | 43 | // A placeholder username validation check 44 | private fun isUserNameValid(username: String): Boolean { 45 | return true 46 | return if (username.contains('@')) { 47 | Patterns.EMAIL_ADDRESS.matcher(username).matches() 48 | } else { 49 | username.isNotBlank() 50 | } 51 | } 52 | 53 | // A placeholder password validation check 54 | private fun isPasswordValid(password: String): Boolean { 55 | return password.length > 5 56 | } 57 | } -------------------------------------------------------------------------------- /app/app/src/main/java/cn/openp2p/ui/login/LoginViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package cn.openp2p.ui.login 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import cn.openp2p.data.LoginDataSource 6 | import cn.openp2p.data.LoginRepository 7 | 8 | /** 9 | * ViewModel provider factory to instantiate LoginViewModel. 10 | * Required given LoginViewModel has a non-empty constructor 11 | */ 12 | class LoginViewModelFactory : ViewModelProvider.Factory { 13 | 14 | @Suppress("UNCHECKED_CAST") 15 | override fun create(modelClass: Class): T { 16 | if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { 17 | return LoginViewModel( 18 | loginRepository = LoginRepository( 19 | dataSource = LoginDataSource() 20 | ) 21 | ) as T 22 | } 23 | throw IllegalArgumentException("Unknown ViewModel class") 24 | } 25 | } -------------------------------------------------------------------------------- /app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/app/src/main/res/drawable/icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/app/src/main/res/layout/activity_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 24 | 25 |