Skip to content

Commit

Permalink
Fix text formatting in .remoteAddress in ServerWebSocket and Socket
Browse files Browse the repository at this point in the history
Fixes #2387
  • Loading branch information
Jarred-Sumner committed Mar 14, 2023
1 parent f63c262 commit 4c38798
Show file tree
Hide file tree
Showing 7 changed files with 132 additions and 43 deletions.
20 changes: 12 additions & 8 deletions src/bun.js/api/bun/socket.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1296,16 +1296,20 @@ fn NewSocket(comptime ssl: bool) type {
return JSValue.jsUndefined();
}

var buf: [512]u8 = undefined;
var length: i32 = 512;
this.socket.remoteAddress(&buf, &length);
const address = buf[0..@intCast(usize, @min(length, 0))];
var buf: [64]u8 = [_]u8{0} ** 64;
var length: i32 = 64;
var text_buf: [512]u8 = undefined;

if (address.len == 0) {
return JSValue.jsUndefined();
}
this.socket.remoteAddress(&buf, &length);
const address_bytes = buf[0..@intCast(usize, length)];
const address: std.net.Address = switch (length) {
4 => std.net.Address.initIp4(address_bytes[0..4].*, 0),
16 => std.net.Address.initIp6(address_bytes[0..16].*, 0, 0, 0),
else => return JSValue.jsUndefined(),
};

return ZigString.init(address).toValueGC(globalThis);
const text = bun.fmt.formatIp(address, &text_buf) catch unreachable;
return ZigString.init(text).toValueGC(globalThis);
}

fn writeMaybeCorked(this: *This, buffer: []const u8, is_end: bool) i32 {
Expand Down
17 changes: 11 additions & 6 deletions src/bun.js/api/server.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3918,13 +3918,18 @@ pub const ServerWebSocket = struct {
return JSValue.jsUndefined();
}

var buf: [512]u8 = undefined;
const address = this.websocket.getRemoteAddress(&buf);
if (address.len == 0) {
return JSValue.jsUndefined();
}
var buf: [64]u8 = [_]u8{0} ** 64;
var text_buf: [512]u8 = undefined;

const address_bytes = this.websocket.getRemoteAddress(&buf);
const address: std.net.Address = switch (address_bytes.len) {
4 => std.net.Address.initIp4(address_bytes[0..4].*, 0),
16 => std.net.Address.initIp6(address_bytes[0..16].*, 0, 0, 0),
else => return JSValue.jsUndefined(),
};

return ZigString.init(address).toValueGC(globalThis);
const text = bun.fmt.formatIp(address, &text_buf) catch unreachable;
return ZigString.init(text).toValueGC(globalThis);
}
};

Expand Down
23 changes: 3 additions & 20 deletions src/bun.js/node/node_os.zig
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,10 @@ pub const Os = struct {
// the address and cidr values can be slices into this same buffer
// e.g. addr_str = "192.168.88.254", cidr_str = "192.168.88.254/24"
var buf: [64]u8 = undefined;
const addr_str = formatAddress(addr, &buf) catch unreachable;
const addr_str = bun.fmt.formatIp(addr, &buf) catch unreachable;
var cidr = JSC.JSValue.null;
if (maybe_suffix) |suffix| {
//NOTE addr_str might not start at buf[0] due to slicing in formatAddress
//NOTE addr_str might not start at buf[0] due to slicing in formatIp
const start = @ptrToInt(addr_str.ptr) - @ptrToInt(&buf[0]);
// Start writing the suffix immediately after the address
const suffix_str = std.fmt.bufPrint(buf[start + addr_str.len ..], "/{}", .{suffix}) catch unreachable;
Expand All @@ -476,7 +476,7 @@ pub const Os = struct {
// netmask <string> The IPv4 or IPv6 network mask
{
var buf: [64]u8 = undefined;
const str = formatAddress(netmask, &buf) catch unreachable;
const str = bun.fmt.formatIp(netmask, &buf) catch unreachable;
interface.put(globalThis, JSC.ZigString.static("netmask"), JSC.ZigString.init(str).withEncoding().toValueGC(globalThis));
}

Expand Down Expand Up @@ -723,23 +723,6 @@ pub const Os = struct {
}
};

fn formatAddress(address: std.net.Address, into: []u8) ![]u8 {
// std.net.Address.format includes `:<port>` and square brackets (IPv6)
// while Node does neither. This uses format then strips these to bring
// the result into conformance with Node.
var result = try std.fmt.bufPrint(into, "{}", .{address});

// Strip `:<port>`
if (std.mem.lastIndexOfScalar(u8, result, ':')) |colon| {
result = result[0..colon];
}
// Strip brackets
if (result[0] == '[' and result[result.len - 1] == ']') {
result = result[1 .. result.len - 1];
}
return result;
}

/// Given a netmask returns a CIDR suffix. Returns null if the mask is not valid.
/// `@TypeOf(mask)` must be one of u32 (IPv4) or u128 (IPv6)
fn netmaskToCIDRSuffix(mask: anytype) ?u8 {
Expand Down
17 changes: 17 additions & 0 deletions src/bun.zig
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,23 @@ pub const path = @import("./resolver/resolve_path.zig");
pub const fmt = struct {
pub usingnamespace std.fmt;

pub fn formatIp(address: std.net.Address, into: []u8) ![]u8 {
// std.net.Address.format includes `:<port>` and square brackets (IPv6)
// while Node does neither. This uses format then strips these to bring
// the result into conformance with Node.
var result = try std.fmt.bufPrint(into, "{}", .{address});

// Strip `:<port>`
if (std.mem.lastIndexOfScalar(u8, result, ':')) |colon| {
result = result[0..colon];
}
// Strip brackets
if (result[0] == '[' and result[result.len - 1] == ']') {
result = result[1 .. result.len - 1];
}
return result;
}

// https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster/
pub fn fastDigitCount(x: u64) u64 {
const table = [_]u64{
Expand Down
16 changes: 7 additions & 9 deletions src/deps/uws.zig
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub fn NewSocketHandler(comptime ssl: bool) type {
this.socket,
);
}
pub fn remoteAddress(this: ThisSocket, buf: [*]u8, length: [*c]i32) void {
pub fn remoteAddress(this: ThisSocket, buf: [*]u8, length: *i32) void {
return us_socket_remote_address(
comptime ssl_int,
this.socket,
Expand Down Expand Up @@ -809,8 +809,8 @@ pub const AnyWebSocket = union(enum) {

pub fn getRemoteAddress(this: AnyWebSocket, buf: []u8) []u8 {
return switch (this) {
.ssl => this.ssl.getRemoteAddressAsText(buf),
.tcp => this.tcp.getRemoteAddressAsText(buf),
.ssl => this.ssl.getRemoteAddress(buf),
.tcp => this.tcp.getRemoteAddress(buf),
};
}
};
Expand Down Expand Up @@ -1614,12 +1614,10 @@ pub fn NewApp(comptime ssl: bool) type {
return uws_ws_get_buffered_amount(ssl_flag, this.raw());
}
pub fn getRemoteAddress(this: *WebSocket, buf: []u8) []u8 {
return buf[0..uws_ws_get_remote_address(ssl_flag, this.raw(), &buf.ptr)];
}

pub fn getRemoteAddressAsText(this: *WebSocket, buf: []u8) []u8 {
var copy = buf;
return buf[0..uws_ws_get_remote_address_as_text(ssl_flag, this.raw(), &copy.ptr)];
var ptr: [*]u8 = undefined;
const len = uws_ws_get_remote_address(ssl_flag, this.raw(), &ptr);
bun.copy(u8, buf, ptr[0..len]);
return buf[0..len];
}
};
};
Expand Down
52 changes: 52 additions & 0 deletions test/js/bun/net/tcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,58 @@ import * as JSC from "bun:jsc";

var decoder = new TextDecoder();

it("remoteAddress works", async () => {
var resolve: () => void, reject: (e: any) => void;
var remaining = 2;
var prom = new Promise<void>((resolve1, reject1) => {
resolve = () => {
if (--remaining === 0) resolve1();
};
reject = reject1;
});
let server = Bun.listen({
socket: {
open(ws) {
try {
expect(ws.remoteAddress).toBe("127.0.0.1");
resolve();
} catch (e) {
reject(e);

return;
} finally {
setTimeout(() => server.stop(true), 0);
}
},
close() {},
data() {},
},
port: 0,
hostname: "localhost",
});

await Bun.connect({
socket: {
open(ws) {
try {
expect(ws.remoteAddress).toBe("127.0.0.1");
resolve();
} catch (e) {
reject(e);
return;
} finally {
ws.end();
}
},
data() {},
close() {},
},
hostname: server.hostname,
port: server.port,
});
await prom;
});

it("echo server 1 on 1", async () => {
// wrap it in a separate closure so the GC knows to clean it up
// the sockets & listener don't escape the closure
Expand Down
30 changes: 30 additions & 0 deletions test/js/bun/websocket/websocket-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@ import { gcTick } from "harness";
import { serve } from "bun";

describe("websocket server", () => {
it("remoteAddress works", done => {
let server = Bun.serve({
websocket: {
message() {},
open(ws) {
try {
expect(ws.remoteAddress).toBe("127.0.0.1");
done();
} catch (e) {
done(e);
}
},
close() {},
},
fetch(req, server) {
if (!server.upgrade(req)) {
return new Response(null, { status: 404 });
}
},
port: 0,
});

let z = new WebSocket(`ws://${server.hostname}:${server.port}`);
z.addEventListener("open", () => {
setTimeout(() => z.close(), 0);
});
z.addEventListener("close", () => {
server.stop();
});
});
it("can do publish()", async done => {
var server = serve({
port: 0,
Expand Down

0 comments on commit 4c38798

Please sign in to comment.