[TS] Add a socket handling implementation that uses Node.JS's `net`
[ldk-java] / node-net / net.mts
1 import * as ldk from "lightningdevkit";
2 import * as net from "net";
3
4 /**
5  * Handles TCP connections using Node.JS's 'net' module given an `ldk.PeerManager`.
6  */
7 export class NodeLDKNet {
8         private ping_timer;
9         private servers: net.Server[];
10         public constructor(public peer_manager: ldk.PeerManager) {
11                 this.ping_timer = setInterval(function() {
12                         peer_manager.timer_tick_occurred();
13                         peer_manager.process_events();
14                 }, 10_000);
15                 this.servers = [];
16         }
17
18         /**
19          * Disconnects all connections and releases all resources for this net handler.
20          */
21         public stop() {
22                 clearInterval(this.ping_timer);
23                 for (const server of this.servers) {
24                         server.close();
25                 }
26                 this.peer_manager.disconnect_all_peers();
27         }
28
29         /**
30          * Processes any pending events for the PeerManager, sending queued messages.
31          * You should call this (or peer_manager.process_events()) any time you take an action which
32          * is likely to generate messages to send (eg send a payment, processing payment forwards,
33          * etc).
34          */
35         public process_events() { this.peer_manager.process_events(); }
36
37         private descriptor_count = BigInt(0);
38         private get_descriptor(socket: net.Socket): ldk.SocketDescriptor {
39                 const this_index = this.descriptor_count;
40                 this.descriptor_count += BigInt(1);
41
42                 socket.setNoDelay(true);
43
44                 const this_pm = this.peer_manager;
45                 var sock_write_waiting = false;
46
47                 let descriptor = ldk.SocketDescriptor.new_impl ({
48                         send_data(data: Uint8Array, resume_read: boolean): number {
49                                 if (resume_read) socket.resume();
50
51                                 if (sock_write_waiting) return 0;
52                                 const written = socket.write(data);
53                                 if (!written) sock_write_waiting = true;
54                                 return data.length;
55                         },
56                         disconnect_socket(): void {
57                                 socket.destroy();
58                         },
59                         eq(other: ldk.SocketDescriptor): boolean {
60                                 return other.hash() == this.hash();
61                         },
62                         hash(): bigint {
63                                 return this_index;
64                         }
65                 } as ldk.SocketDescriptorInterface);
66
67                 socket.on("drain", function() {
68                         if (sock_write_waiting) {
69                                 if (!this_pm.write_buffer_space_avail(descriptor).is_ok()) {
70                                         descriptor.disconnect_socket();
71                                 }
72                         }
73                 });
74
75                 socket.on("data", function(data) {
76                         const res = this_pm.read_event(descriptor, data);
77                         if (!res.is_ok()) descriptor.disconnect_socket();
78                         else if ((res as ldk.Result_boolPeerHandleErrorZ_OK).res) socket.pause();
79                         this_pm.process_events();
80                 });
81
82                 socket.on("close", function() {
83                         this_pm.socket_disconnected(descriptor);
84                 });
85
86                 return descriptor;
87         }
88
89         private static v4_addr_from_ip(ip: string, port: number): ldk.NetAddress {
90                 const sockaddr = ip.split(".").map(parseFloat);
91                 return ldk.NetAddress.constructor_ipv4(new Uint8Array(sockaddr), port);
92         }
93         private static v6_addr_from_ip(ip: string, port: number): ldk.NetAddress {
94                 const sockaddr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
95                 const halves = ip.split("::"); // either one or two elements
96                 const first_half = halves[0].split(":");
97                 for (var idx = 0; idx < first_half.length; idx++) {
98                         const v = parseInt(first_half[idx], 16);
99                         sockaddr[idx*2] = v >> 8;
100                         sockaddr[idx*2 + 1] = v & 0xff;
101                 }
102                 if (halves.length == 2) {
103                         const second_half = halves[1].split(":");
104                         for (var idx = 0; idx < second_half.length; idx++) {
105                                 const v = parseInt(second_half[second_half.length - idx - 1], 16);
106                                 sockaddr[14 - idx*2] = v >> 8;
107                                 sockaddr[15 - idx*2] = v & 0xff;
108                         }
109                 }
110                 return ldk.NetAddress.constructor_ipv6(new Uint8Array(sockaddr), port);
111         }
112
113         private static get_addr_from_socket(socket: net.Socket): ldk.Option_NetAddressZ {
114                 const addr = socket.remoteAddress;
115                 if (addr === undefined)
116                         return ldk.Option_NetAddressZ.constructor_none();
117                 if (net.isIPv4(addr)) {
118                         return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v4_addr_from_ip(addr, socket.remotePort));
119                 }
120                 if (net.isIPv6(addr)) {
121                         return ldk.Option_NetAddressZ.constructor_some(NodeLDKNet.v6_addr_from_ip(addr, socket.remotePort));
122                 }
123                 return ldk.Option_NetAddressZ.constructor_none();
124         }
125
126         /**
127          * Binds a listener on the given host and port, accepting incoming connections.
128          */
129         public async bind_listener(host: string, port: number) {
130                 const this_handler = this;
131                 const server = net.createServer(function(incoming_sock: net.Socket) {
132                         const descriptor = this_handler.get_descriptor(incoming_sock);
133                         const res = this_handler.peer_manager
134                                 .new_inbound_connection(descriptor, NodeLDKNet.get_addr_from_socket(incoming_sock));
135                         if (!res.is_ok()) descriptor.disconnect_socket();
136                 });
137                 const servers_list = this.servers;
138                 return new Promise<void>((resolve, reject) => {
139                         server.on("error", function() {
140                                 reject();
141                                 server.close();
142                         });
143                         server.on("listening", function() {
144                                 servers_list.push(server);
145                                 resolve();
146                         });
147                         server.listen(port, host);
148                 });
149         }
150
151         /**
152          * Establishes an outgoing connection to the given peer at the given host and port.
153          *
154          * Note that the peer will not appear in the PeerManager peers list until the socket has
155          * connected and the initial handshake completes.
156          */
157         public async connect_peer(host: string, port: number, peer_node_id: Uint8Array) {
158                 const this_handler = this;
159                 const sock = new net.Socket();
160                 const res = new Promise<void>((resolve, reject) => {
161                         sock.on("connect", function() { resolve(); });
162                         sock.on("error", function() { reject(); });
163                 });
164                 sock.connect(port, host, function() {
165                         const descriptor = this_handler.get_descriptor(sock);
166                         const res = this_handler.peer_manager
167                                 .new_outbound_connection(peer_node_id, descriptor, NodeLDKNet.get_addr_from_socket(sock));
168                         if (!res.is_ok()) descriptor.disconnect_socket();
169                         else {
170                                 const bytes = (res as ldk.Result_CVec_u8ZPeerHandleErrorZ_OK).res;
171                                 const send_res = descriptor.send_data(bytes, true);
172                                 console.assert(send_res == bytes.length);
173                         }
174                 });
175                 return res;
176         }
177 }