Repository navigation
use std.Io.net.IpAddress instead of making our own
#3056
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 52 commits
Commits
Show all changes
55 commits
Select commit
Hold shift + click to select a range
72562a7
Initial setup
codemob-dev 21daaa2
Comp the ile
codemob-dev dc579d0
Memory leek
codemob-dev 89db35d
The stupid is stupiding
codemob-dev 4bce3bf
aaaa
codemob-dev e350bbd
I dids the stupids
codemob-dev b16d22e
I dids the even more stupids
codemob-dev c4d9f86
u32 in a pointer costume 🔥🔥
codemob-dev c073f08
Remove unnececarry error
codemob-dev 255aaef
Progress on fixing things
codemob-dev d88f7de
Merge branch 'master' into ip-banning
codemob-dev 5dc3931
Stupidstupidstupidstupid
codemob-dev c5e132b
Merge branch 'master' into ip-banning
codemob-dev aa67a3b
Progress towards fixing things
codemob-dev 7e43926
Make the tests run
codemob-dev f1268b4
More tests
codemob-dev 6c60dde
kick player
codemob-dev 110eacd
Formatting
codemob-dev f7bbf5b
Remove ip banning stuff
codemob-dev bc423d5
Merge branch 'master' into address-struct
codemob-dev 5d654a5
Finish reverting stuff
codemob-dev 0ebc51c
Merge branch 'master' into address-struct
codemob-dev 81f40ff
Better errors
codemob-dev dfaaecd
Refactor functions
codemob-dev db6b4de
Formatting
codemob-dev d132900
Merge branch 'master' into address-struct
codemob-dev 7f815a2
Fix things (WIP)
codemob-dev 63d0254
Redo a lot of stuff
codemob-dev 862c865
redundant thing
codemob-dev b6371b4
Use the local allocator
codemob-dev b0bc4ff
remove unneeded thing
codemob-dev 58c26dc
oops
codemob-dev acd636e
Remove accidental changes
codemob-dev 1bc5f68
Formatting
codemob-dev dd28790
Add default port parameter
codemob-dev d678bd2
Fix resolve tests
codemob-dev 757969f
Merge branch 'master' into address-struct
codemob-dev ae2cdf1
Remove duplicate ProtocolTask struct from merge
codemob-dev 76f6933
Fix some accidental removed code from merging
codemob-dev 71b9faa
Fix parseInner function
codemob-dev 664f668
Use Socket.resolveIP
codemob-dev 6e0ab76
It compiles, yay!
codemob-dev c0e8ab7
Improve the tests
codemob-dev cfa5c08
Formatting
codemob-dev 2933138
use Ip4Address for inner
Wunka 99a5b3d
use addressToPosix at some places to prepare for ip6 enabelment
Wunka 76c15c1
type
Wunka bbb22f1
use generalized IpAddress instead of Ip4Address
Wunka e340562
fix tests
Wunka 08bafad
Merge branch 'master' into innerIpAddress
Wunka d823a2a
remove parse as quantum requested from codemob
Wunka 1175401
remove faulty tests
Wunka 9027d9e
fix
Wunka 12ccadd
fix 2
Wunka d4fbcfe
Merge remote-tracking branch 'upstream/master' into innerIpAddress
Wunka File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| const builtin = @import("builtin"); | ||
| const std = @import("std"); | ||
| const Atomic = std.atomic.Value; | ||
| const IpAddress = std.Io.net.IpAddress; | ||
|
|
||
| const main = @import("main"); | ||
| const game = main.game; | ||
|
|
@@ -115,10 +116,11 @@ const Socket = struct { | |
| } | ||
| } | ||
|
|
||
| fn send(self: Socket, data: []const u8, destination: Address) void { | ||
| const addr = posix.sockaddr.in{ | ||
| .port = @byteSwap(destination.port), | ||
| .addr = destination.ip, | ||
| fn send(self: Socket, data: []const u8, destination: SocketAddress) void { | ||
| const addr = blk: { | ||
| var posixAddr: std.Io.Threaded.PosixAddress = undefined; | ||
| _ = std.Io.Threaded.addressToPosix(&destination.address, &posixAddr); | ||
| break :blk posixAddr.in; | ||
| }; | ||
| if (builtin.os.tag == .windows) { | ||
| const result = c.sendto(self.socketID, data.ptr, @intCast(data.len), 0, @ptrCast(&addr), @sizeOf(posix.sockaddr.in)); | ||
|
|
@@ -141,7 +143,7 @@ const Socket = struct { | |
| } | ||
| } | ||
|
|
||
| fn receive(self: Socket, buffer: []u8, timeout: i32, resultAddress: *Address) ![]u8 { | ||
| fn receive(self: Socket, buffer: []u8, timeout: i32, resultAddress: *SocketAddress) ![]u8 { | ||
| if (builtin.os.tag == .windows) { // Of course Windows always has it's own special thing. | ||
| var pfd = [1]c.pollfd{ | ||
| .{.fd = self.socketID, .events = c.POLLRDNORM | c.POLLRDBAND, .revents = undefined}, | ||
|
|
@@ -181,22 +183,21 @@ const Socket = struct { | |
| } | ||
| } | ||
| }; | ||
| resultAddress.ip = addr.addr; | ||
| resultAddress.port = @byteSwap(addr.port); | ||
| resultAddress.address = std.Io.Threaded.addressFromPosix(&std.Io.Threaded.PosixAddress{.in = addr}); | ||
| return buffer[0..length]; | ||
| } | ||
|
|
||
| fn resolveIP(name: []const u8) !u32 { | ||
| fn resolveIP(name: []const u8, port: u16) !IpAddress { | ||
| var nameBuf: [255]u8 = undefined; | ||
| var buf: [16]std.Io.net.HostName.LookupResult = undefined; | ||
| var resultQueue = std.Io.Queue(std.Io.net.HostName.LookupResult).init(&buf); | ||
| try std.Io.net.HostName.lookup(try .init(name), main.io, &resultQueue, .{.canonical_name_buffer = &nameBuf, .port = 0}); | ||
| try std.Io.net.HostName.lookup(try .init(name), main.io, &resultQueue, .{.canonical_name_buffer = &nameBuf, .port = port}); | ||
| while (true) { | ||
| const entry = resultQueue.getOneUncancelable(main.io) catch break; | ||
| switch (entry) { | ||
| .address => |addr| { | ||
| if (addr != .ip4) continue; | ||
| return std.mem.bytesToValue(u32, addr.ip4.bytes[0..4]); | ||
| return addr; | ||
| }, | ||
| .canonical_name => {}, | ||
| } | ||
|
|
@@ -237,24 +238,64 @@ pub fn deinit() void { | |
| c.mbedtls_psa_crypto_free(); | ||
| } | ||
|
|
||
| pub const Address = struct { | ||
| ip: u32, | ||
| port: u16, | ||
| pub const SocketAddress = struct { | ||
| address: IpAddress, | ||
| isSymmetricNAT: bool = false, | ||
|
|
||
| pub const localHost = 0x0100007f; | ||
|
|
||
| pub fn format(self: Address, writer: anytype) !void { | ||
| pub fn format(self: SocketAddress, writer: anytype) !void { | ||
| if (self.isSymmetricNAT) { | ||
| try writer.print("{}.{}.{}.{}:?{}", .{self.ip & 255, self.ip >> 8 & 255, self.ip >> 16 & 255, self.ip >> 24, self.port}); | ||
| try writer.print("{f}?", .{self.address}); | ||
| } else { | ||
| try writer.print("{}.{}.{}.{}:{}", .{self.ip & 255, self.ip >> 8 & 255, self.ip >> 16 & 255, self.ip >> 24, self.port}); | ||
| try writer.print("{f}", .{self.address}); | ||
| } | ||
| } | ||
|
|
||
| fn parseInner(string: []const u8, defaultPort: ?u16) !struct { ip: []const u8, isSymmetricNAT: bool, port: u16 } { | ||
| var parts = std.mem.splitScalar(u8, string, ':'); | ||
| const ip = parts.first(); | ||
| var portString = parts.rest(); | ||
| var isSymmetricNAT = false; | ||
| if (portString.len > 0 and portString[portString.len - 1] == '?') { | ||
| isSymmetricNAT = true; | ||
| portString = portString[0 .. portString.len - 1]; | ||
| } | ||
| const port = try (std.fmt.parseUnsigned(u16, portString, 10) catch |err| (defaultPort orelse err)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of ignoring the error when a defaultPort is present, this should only use the default port when the portString.len == 0 |
||
| return .{ | ||
| .ip = ip, | ||
| .isSymmetricNAT = isSymmetricNAT, | ||
| .port = port, | ||
| }; | ||
| } | ||
|
|
||
| pub fn resolve(string: []const u8, defaultPort: ?u16) !SocketAddress { | ||
| const innerResult = try parseInner(string, defaultPort); | ||
| return .{ | ||
| .address = try Socket.resolveIP(innerResult.ip, innerResult.port), | ||
| .isSymmetricNAT = innerResult.isSymmetricNAT, | ||
| }; | ||
| } | ||
|
|
||
| // copied from zig std, just without the port == port | ||
| pub fn eqlInnerWithoutPort(self: *const SocketAddress, other: *const SocketAddress) bool { | ||
| return switch (self.address) { | ||
| .ip4 => |a_ip4| switch (other.address) { | ||
| .ip4 => |b_ip4| { | ||
| const a_int: u32 = @bitCast(a_ip4.bytes); | ||
| const b_int: u32 = @bitCast(b_ip4.bytes); | ||
| return a_int == b_int; | ||
| }, | ||
| else => false, | ||
| }, | ||
| .ip6 => |a_ip6| switch (other.address) { | ||
| .ip6 => |b_ip6| std.mem.eql(u8, &a_ip6.bytes, &b_ip6.bytes), | ||
| else => false, | ||
| }, | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| const Request = struct { | ||
| address: Address, | ||
| address: SocketAddress, | ||
| data: []const u8, | ||
| requestNotifier: main.utils.Condition = .{}, | ||
| }; | ||
|
|
@@ -364,8 +405,8 @@ const stun = struct { // MARK: stun | |
| const XOR_MAPPED_ADDRESS: u16 = 0x0020; | ||
| const MAGIC_COOKIE = [_]u8{0x21, 0x12, 0xA4, 0x42}; | ||
|
|
||
| fn requestAddress(connection: *ConnectionManager) Address { | ||
| var oldAddress: ?Address = null; | ||
| fn requestAddress(connection: *ConnectionManager) SocketAddress { | ||
| var oldAddress: ?SocketAddress = null; | ||
| var seed: [std.Random.DefaultCsprng.secret_seed_length]u8 = @splat(0); | ||
| std.mem.writeInt(i128, seed[0..16], main.timestamp().toMilliseconds(), builtin.cpu.arch.endian()); // Not the best seed, but it's not that important. | ||
| var random = std.Random.DefaultCsprng.init(seed); | ||
|
|
@@ -380,14 +421,9 @@ const stun = struct { // MARK: stun | |
| }; | ||
| random.fill(data[8..]); // Fill the transaction ID. | ||
|
|
||
| var splitter = std.mem.splitScalar(u8, server, ':'); | ||
| const ip = splitter.first(); | ||
| const serverAddress = Address{ | ||
| .ip = Socket.resolveIP(ip) catch |err| { | ||
| std.log.warn("Cannot resolve STUN server address: {s}, error: {s}", .{ip, @errorName(err)}); | ||
| continue; | ||
| }, | ||
| .port = std.fmt.parseUnsigned(u16, splitter.rest(), 10) catch 3478, | ||
| const serverAddress = SocketAddress.resolve(server, null) catch |err| { | ||
| std.log.warn("Cannot resolve STUN server address: {s}, error: {s}", .{server, @errorName(err)}); | ||
| continue; | ||
| }; | ||
| if (connection.sendRequest(main.globalAllocator, &data, serverAddress, .fromMilliseconds(500))) |answer| { | ||
| defer main.globalAllocator.free(answer); | ||
|
|
@@ -400,7 +436,7 @@ const stun = struct { // MARK: stun | |
| continue; | ||
| }; | ||
| if (oldAddress) |other| { | ||
| if (other.ip == result.ip and other.port == result.port) { | ||
| if (other.address.eql(&result.address)) { | ||
| return result; | ||
| } else { | ||
| std.log.warn("Detected symmetric NAT. UDP-holepunching may not work reliably", .{}); | ||
|
|
@@ -414,10 +450,10 @@ const stun = struct { // MARK: stun | |
| std.log.warn("Couldn't reach STUN server: {s}", .{server}); | ||
| } | ||
| } | ||
| return Address{.ip = Socket.resolveIP("127.0.0.1") catch unreachable, .port = settings.defaultPort}; // TODO: Return ip address in LAN. | ||
| return SocketAddress{.address = .{.ip4 = .loopback(settings.defaultPort)}}; // TODO: Return ip address in LAN. | ||
| } | ||
|
|
||
| fn findIPPort(_data: []const u8) !Address { | ||
| fn findIPPort(_data: []const u8) !SocketAddress { | ||
| var data = _data[20..]; // Skip the header. | ||
| while (data.len > 0) { | ||
| const typ = std.mem.readInt(u16, data[0..2], .big); | ||
|
|
@@ -437,9 +473,13 @@ const stun = struct { // MARK: stun | |
| addressData[4] ^= MAGIC_COOKIE[2]; | ||
| addressData[5] ^= MAGIC_COOKIE[3]; | ||
| } | ||
| return Address{ | ||
| .port = std.mem.readInt(u16, addressData[0..2], .big), | ||
| .ip = std.mem.readInt(u32, addressData[2..6], builtin.cpu.arch.endian()), // Needs to stay in big endian → native. | ||
| return SocketAddress{ | ||
| .address = .{ | ||
| .ip4 = .{ | ||
| .port = std.mem.readInt(u16, addressData[0..2], .big), | ||
| .bytes = std.mem.toBytes(std.mem.readInt(u32, addressData[2..6], builtin.cpu.arch.endian())), // Needs to stay in big endian → native. | ||
| }, | ||
| }, | ||
| }; | ||
| } else if (data[1] == 0x02) { | ||
| data = data[(len + 3) & ~@as(usize, 3) ..]; // Pad to 32 Bit. | ||
|
|
@@ -472,7 +512,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| socket: Socket = undefined, | ||
| thread: std.Thread = undefined, | ||
| threadId: std.Thread.Id = undefined, | ||
| externalAddress: Address = undefined, | ||
| externalAddress: SocketAddress = undefined, | ||
| online: Atomic(bool) = .init(false), | ||
| running: Atomic(bool) = .init(false), | ||
|
|
||
|
|
@@ -493,7 +533,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
|
|
||
| const PacketSendRequest = struct { | ||
| data: []const u8, | ||
| target: Address, | ||
| target: SocketAddress, | ||
| time: i64, | ||
|
|
||
| fn compare(_: void, a: PacketSendRequest, b: PacketSendRequest) std.math.Order { | ||
|
|
@@ -562,7 +602,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| } | ||
| } | ||
|
|
||
| pub fn send(self: *ConnectionManager, data: []const u8, target: Address, nanoTime: ?i64) void { | ||
| pub fn send(self: *ConnectionManager, data: []const u8, target: SocketAddress, nanoTime: ?i64) void { | ||
| if (nanoTime) |time| { | ||
| self.mutex.lock(); | ||
| defer self.mutex.unlock(); | ||
|
|
@@ -576,7 +616,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| } | ||
| } | ||
|
|
||
| pub fn sendRequest(self: *ConnectionManager, allocator: NeverFailingAllocator, data: []const u8, target: Address, timeout: std.Io.Duration) ?[]const u8 { | ||
| pub fn sendRequest(self: *ConnectionManager, allocator: NeverFailingAllocator, data: []const u8, target: SocketAddress, timeout: std.Io.Duration) ?[]const u8 { | ||
| self.socket.send(data, target); | ||
| var request = Request{.address = target, .data = data}; | ||
| { | ||
|
|
@@ -612,7 +652,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| self.mutex.lock(); | ||
| defer self.mutex.unlock(); | ||
| for (self.connections.items) |other| { | ||
| if (other.remoteAddress.ip == conn.remoteAddress.ip and other.remoteAddress.port == conn.remoteAddress.port) return error.AlreadyConnected; | ||
| if (other.remoteAddress.address.eql(&conn.remoteAddress.address)) return error.AlreadyConnected; | ||
| } | ||
| self.connections.append(main.globalAllocator, conn); | ||
| } | ||
|
|
@@ -636,17 +676,17 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| } | ||
| } | ||
|
|
||
| fn onReceive(self: *ConnectionManager, data: []const u8, source: Address) void { | ||
| fn onReceive(self: *ConnectionManager, data: []const u8, source: SocketAddress) void { | ||
| std.debug.assert(self.threadId == std.Thread.getCurrentId()); | ||
| self.mutex.lock(); | ||
|
|
||
| for (self.connections.items) |conn| { | ||
| if (conn.remoteAddress.ip == source.ip) { | ||
| if (conn.remoteAddress.eqlInnerWithoutPort(&source)) { | ||
| if (conn.bruteforcingPort) { | ||
| conn.remoteAddress.port = source.port; | ||
| conn.remoteAddress.address.setPort(source.address.getPort()); | ||
| conn.bruteforcingPort = false; | ||
| } | ||
| if (conn.remoteAddress.port == source.port) { | ||
| if (conn.remoteAddress.address.getPort() == source.address.getPort()) { | ||
| self.mutex.unlock(); | ||
| conn.receive(data); | ||
| return; | ||
|
|
@@ -657,15 +697,15 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| defer self.mutex.unlock(); | ||
| // Check if it's part of an active request: | ||
| for (self.requests.items) |request| { | ||
| if (request.address.ip == source.ip and request.address.port == source.port) { | ||
| if (request.address.address.eql(&source.address)) { | ||
| request.data = main.globalAllocator.dupe(u8, data); | ||
| request.requestNotifier.signal(); | ||
| return; | ||
| } | ||
| } | ||
| if (self.online.load(.acquire) and source.ip == self.externalAddress.ip and source.port == self.externalAddress.port) return; | ||
| if (self.online.load(.acquire) and source.address.eql(&self.externalAddress.address)) return; | ||
| } | ||
| if (self.allowNewConnections or source.ip == Address.localHost) { | ||
| if (self.allowNewConnections or source.address.eql(&IpAddress{.ip4 = .loopback(source.address.getPort())})) { | ||
| if (data.len != 0 and data[0] == @intFromEnum(Connection.ChannelId.init)) { | ||
| const ip = std.fmt.allocPrint(main.stackAllocator.allocator, "{f}", .{source}) catch unreachable; | ||
| defer main.stackAllocator.free(ip); | ||
|
|
@@ -693,7 +733,7 @@ pub const ConnectionManager = struct { // MARK: ConnectionManager | |
| while (self.running.load(.monotonic)) { | ||
| main.heap.GarbageCollection.syncPoint(); | ||
| self.waitingToFinishReceive.broadcast(); | ||
| var source: Address = undefined; | ||
| var source: SocketAddress = undefined; | ||
| if (self.socket.receive(&self.receiveBuffer, 1, &source)) |data| { | ||
| self.onReceive(data, source); | ||
| } else |err| { | ||
|
|
@@ -1433,7 +1473,7 @@ pub const Connection = struct { // MARK: Connection | |
| manager: *ConnectionManager, | ||
| user: ?*main.server.User, | ||
|
|
||
| remoteAddress: Address, | ||
| remoteAddress: SocketAddress, | ||
| bruteforcingPort: bool = false, | ||
| bruteForcedPortRange: u16 = 0, | ||
|
|
||
|
|
@@ -1493,20 +1533,8 @@ pub const Connection = struct { // MARK: Connection | |
| try result.secureChannel.init(main.random.nextInt(SequenceIndex, &main.seed), 10*ms, .secure, if (user != null) .server else .client); | ||
| errdefer result.secureChannel.deinit(); | ||
| if (result.connectionIdentifier == 0) result.connectionIdentifier = 1; | ||
|
|
||
| var splitter = std.mem.splitScalar(u8, ipPort, ':'); | ||
| const ip = splitter.first(); | ||
| result.remoteAddress.ip = try Socket.resolveIP(ip); | ||
| var port = splitter.rest(); | ||
| if (port.len != 0 and port[0] == '?') { | ||
| result.remoteAddress.isSymmetricNAT = true; | ||
| result.bruteforcingPort = true; | ||
| port = port[1..]; | ||
| } | ||
| result.remoteAddress.port = std.fmt.parseUnsigned(u16, port, 10) catch blk: { | ||
| if (ip.len != ipPort.len) std.log.err("Could not parse port \"{s}\". Using default port instead.", .{port}); | ||
| break :blk settings.defaultPort; | ||
| }; | ||
| result.remoteAddress = try SocketAddress.resolve(ipPort, settings.defaultPort); | ||
| result.bruteforcingPort = result.remoteAddress.isSymmetricNAT; | ||
|
|
||
| try result.manager.addConnection(result); | ||
| return result; | ||
|
|
@@ -1850,3 +1878,49 @@ pub const Connection = struct { // MARK: Connection | |
| std.log.info("Disconnected", .{}); | ||
| } | ||
| }; | ||
|
|
||
| test "Resolve address" { | ||
| const addresses: [4][]const u8 = .{ | ||
| "127.0.0.1", | ||
| "123.1.111.222", | ||
| "1.1.1.1", | ||
| "0.0.0.0", | ||
| }; | ||
| for (addresses) |addressStr| { | ||
| const parsedAddress = try IpAddress.parse(addressStr, 0); | ||
| const resolvedAddress = try Socket.resolveIP(addressStr, 0); | ||
| try std.testing.expectEqualDeep(parsedAddress, resolvedAddress); | ||
| } | ||
| const resolvedLocalhost = try Socket.resolveIP("localhost", 0); | ||
| try std.testing.expectEqualDeep(IpAddress{.ip4 = .loopback(0)}, resolvedLocalhost); | ||
|
|
||
| const localhostSocketAddresses: [3][]const u8 = .{ | ||
| "localhost", | ||
| "localhost:1234", | ||
| "localhost:11111?", | ||
| }; | ||
| const expectedLocalhostSocketAddresses: [3]SocketAddress = .{ | ||
| .{.address = .{.ip4 = .loopback(888)}}, | ||
| .{.address = .{.ip4 = .loopback(1234)}}, | ||
| .{.address = .{.ip4 = .loopback(11111)}, .isSymmetricNAT = true}, | ||
| }; | ||
| for (localhostSocketAddresses, expectedLocalhostSocketAddresses) |addressStr, expected| { | ||
| const resolvedAddress = try SocketAddress.resolve(addressStr, 888); | ||
| try std.testing.expectEqualDeep(expected, resolvedAddress); | ||
| } | ||
| } | ||
|
|
||
| test "Format address" { | ||
| const socketAddresses: [4][]const u8 = .{ | ||
| "127.0.0.1:1234", | ||
| "123.1.111.222:11111?", | ||
| "1.1.1.1:255", | ||
| "0.0.0.0:3333?", | ||
| }; | ||
| for (socketAddresses) |addressStr| { | ||
| const address = try SocketAddress.resolve(addressStr, null); | ||
| const reformattedAddress = std.fmt.allocPrint(main.heap.testingAllocator.allocator, "{f}", .{address}) catch unreachable; | ||
| defer main.heap.testingAllocator.free(reformattedAddress); | ||
| try std.testing.expectEqualStrings(addressStr, reformattedAddress); | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The length (return value) should probably be passed into the other functions instead of using
@sizeOfThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
both done