 Hello, welcome back. It's time for another one of our TCP streams in In the last twitch stream what we did was we started implementing a user space TCP stack Not really for sort of performance reasons for anything even though that is often why people do that for more because it would be We figured it would be interesting to implement sort of a lower level protocol like look at like Technical specs and protocol definitions. So we were doing basically implementing ours RFC 793, which is the TCP RFC and various other related RFCs We're doing that using ton tap interfaces so ton tap interfaces are basically a way to Have the kernel give a user space process access to raw packets. And so this lets us Interact with our implementation as though it was running in sort of its own kernel somewhere I'm not gonna reiterate all of the stuff from the first stream in this one So I sort of assume that I Sort of assume that we that you've watched part one before this one. So I'm gonna put a link Somewhere to part one. So you should go watch that if you haven't already This video will also be uploaded to YouTube as always so you can always watch this later Before we get started on sort of continue our implementation There are a couple of housekeeping things I want to deal with So the first one is that this will probably be the last stream for a little while I have a pretty major deadline coming up towards the end of April. So next stream probably won't be until May It's a little unfortunate, but but sadly life gets in the way The Really the inject adds into the stream. That's awful And Number two, I feel like we've been running this for long enough now that it seems appropriate that we try to find a name for this thing It's sort of easier to just call it rest live coding streams We do some other stuff too like the video on The how and why of futures for example, and I think it would be good to have some umbrella term for what this Stream is so I'm gonna I'm gonna sort of give a Yak shaving project to the chat over the course of the stream to try to figure out a good name for what the stream Should be referred to as I might even go as far as sort of to create a separate Twitter account or something That you could follow just for updates from the stream We'll see exactly how that works out, but some kind of name would be useful And there's a 13 that's kind of stupid I decided to buy new wheels for my chair and I discovered that the My floor is not level. So every now and again, I'll just sort of Roll away and So if you see me moving Weirdly, then that's why Okay, so with that housekeeping out of the way Are there any questions from the first stream before we get started on this anything that was unclear that you sort of want to know? How works just so we all start out without having burning questions from the very beginning I'll give chat a little while just to See if there's anything you want to Recap before we start chat currently seems mostly busy talking about how to avoid ads on Twitch So I'm gonna assume that there are no particularly burning questions that people have And if you do then just sort of ping me as we go Okay, so Where we ended last time was we wrote this thing called trust Name still under development And we basically just wrote a main function that Runs the core TCP protocol So main will basically just read packets in from the virtual interface that we create so it creates a virtual interface it reads packets in from it and Sort of keeps track of various connection state and then calls into the The TCP implementation we have the TCP stack for the state that we're currently at it gives the packet that it received And that's basically all we have so far For the TCP stack we have support for setting up and tearing down the connection But we do not have any support for data yet I Still vote for thunder as the name Thunder is pretty cool. I agree Yeah, so we have support basically for Establishing and then tearing down a connection but nothing in between the other thing that's a Actually worth pointing out is following the previous stream. There were a bunch of comments on On the YouTube On the YouTube video for the first part where someone pointed out that actually when the RFC talks about Sequence numbers being between two different things There's a well-defined way in which that operation works and specifically instead of this whole business that we Computed and drew out it turns out what you're actually supposed to do is just check whether One minus the other is more than half of the number space And this is sort of apparently so well understood that it's not even in the RFC, which is kind of stupid But such as life so this has been changed. Let me oh So that has now been fixed so if you look at the is between now It is actually a lot easier a lot of simpler than it was before Yeah, for raw packages, I would if you're wondering how to do that I would watch the first stream first Okay, so the question is where we go with this particular stream This might be a little bit shorter than the normal streams, but we'll see how that works out I think what we're going to do in this one is The next thing we sort of need to support is being able to send and receive data because currently all you can do is set Up the connection and tear it down, which isn't particularly helpful So I think we actually need to set up what is effectively a an API for being able to send things and receive packets And we're probably going to model it Mostly on what is in the standard library already. So we're going to end up with something sort of like Standard net TCP stream and standard net TCP listener Instead of this currently we just have this main function, which doesn't really have a good way for you to read or write data And so how about we just get started with that I Think we're going to start a new file here is something like let's call it live bars for now Have you tried BPF? BPF is not really for this particular thing There are rust bindings for BPF. I think Okay, so we sort of want something along the lines of TCP stream and we want something along the lines of TCP listener Right, this is the types of the TCP The TCP provides us is that the standard library TCP implementation Provides us with and ideally we want to support the standard interfaces as much as we can So specifically we sort of want to do Imple read for TCP stream We want to impel write for TCP stream And on TCP listener We want something like a pub, but I guess these are gonna be pub We want something like an accept that takes a mute Self and returns a TCP stream probably an IO result TCP stream So like something along these lines and of course read Just to get the signature for read Read is this So this is an IO result and Right is This So right we're gonna need something along these lines So I think our goal for today is basically to find a way to fill out these now We might not actually be able to get Read and write to fully work because that's a bunch of internal protocol stuff like congestion control But we at least want to figure out how to wire this interface into the implementation. We already have if you remember from last time a Listener a TCP listener Doesn't really do all that much except that actually I should put off my paint program Here so If you recall The way TCP works is you have sort of a client Server although TCP doesn't really call them this But you sort of have a client in a server think of the server as the thing that is listening And the way the TCP handshake works is the client or whoever wants to connect sends a sin packet So this is a TCP packet with the with the sin bit set And the server if they're willing to accept the connection responds with What's known as a sin AC? So this is a packet that has both the sin flag and the AC flag set and the way to interpret this is that this AC This AC is for this sin and Then the client is gonna send back an AC And this AC is for this sin So this is saying I got your sin and I'm I want to connect back to you and this AC is for the sin So this this is known as the three way See if I can do I don't know if that's easier to read probably not the three-way handshake in TCP And what it does is it establishes a two-way connection, right? So think of a sin is I want to talk to you Right and this AC is saying okay, and I want to talk to you Okay, it's sort of the way to read the sequence of events And if you are listening on a given port all that really means is you're willing to send this message So if the server is not listening on a port, it's just not gonna reply to the sin It could respond with like a reset To just directly say that it's not responding or it could just not respond at all. They're both sort of fine I TCP might mandate which one you're supposed to do and Currently what we have our system set up to do is anytime anyone connects to any port We're gonna send AC and set up a connection with them Whereas in reality what we really want is only if the server has created a TCP Listener, wow my handwriting on this is terrible. It's terrible. Not otherwise, too But if we've created a TCP listener on like port 80 for example Then if this packet is for port 80 oh, then and only then do we send the sin back If the sin comes from some other port like 9,000 Right if it comes from 9,000 and there is no TCP listener then we will not respond There's no reason to because we don't want to we haven't said though. We want to accept packets on that port Yeah, I mean chat basically just demonstrated how the TCP Handshake works want to shake hands. Yes puts our month shakes hand. It's like a very it's a very sort of Weird human protocol if you were to try to act it out Okay, so So what this means is that our TCP listener all it's really doing is Almost like marking a Marking a port number as being available And so if a packet comes in from a port that is not marked as available We drop it if it is marked as available, then we sort of enter the whole TCP event loop for that channel And so the TCP listener actually does pre not that not doesn't do all that much The TCP stream on the other hand is basically the thing that contains all of the state associated with this connection So once this is syn Act business has been set up. There's a bunch of state you're keeping like sequence numbers and act numbers and window sizes and all of this business all of that from both sides is going to be incorporated into the TCP stream So that's basically what we want And so let's see how that would actually look in code So If you remember from our main we have this connections map that is a map from That is a map from a quad So a quad identifies a particular connection to a TCP connection And if you look down here What we did down here was if the entry is vacant so if we get a We get a TCP packet for For a quad that we don't know about then we just immediately call accept We sort of want to change this right like we want to change this to be if it's vacant so if there's no current connection and We've said that we're willing to create a connection And So we're probably gonna want most of this the other thing that's a little weird here is the Tunnel we have is basically single threaded right whereas we sort of want the user to be able to create multiple different connections all over the place and So we might need to have this be almost like a Almost like a Sort of a main event loop that runs on one thread and all of the TCP listeners and streams are gonna interact with that thread That might end up causing us a little bit in terms of performance, but it might be a good way to model the system at least initially So For that to work What we will probably need is we'll need something like a Pub struct endpoint Or endpoint might be a bad name for this but Interface maybe or network it's called an interface for now so the idea would be that an interface is a Thing that can receive packets and it sort of holds all the Connection state information for all the currently open connections And so if you want a TCP listener you have to call listen on the interface and that will give you The corresponding TCP listener object And so the way to think about this is something along the lines of this That's also a good question what the types of these are Actually, let's not put those in just yet So we're gonna have something like impaled probably default for interface and What that is going to do is this and then create an interface that has connections Nick and so Maybe oh actually we need Cargo Toml we need to declare that we also have a library And we also have a bin. I don't know how cargo is gonna feel about this binary name Is gonna also be trust Actually, let's me do this. Oh Sure These are for the time being unimplemented and of course these fields need to exist in the first place So connections we know is going to be one of these So connections is going to be a default default I don't actually remember the type of this. So this is a neat little trick that you can play No, actually this I do know the type of this is a UA So in this case, I don't actually know what the I don't actually know what the type of Nick here is and so I'm sort of gonna cheat. I'm gonna cheat by Actually, this has to be a new can't be that because we have to be able to return an error This is gonna be an I or results this So the the trick here is I don't actually know the type that without packet info returns I could look it up But instead what I'm gonna do is to set the type to be unit and then try to compile and then it's gonna tell me This is the type. I actually had great. So now we can just replace this with that Whole driven development. Yeah, basically Okay, so you create a new interface and what that's gonna do is it's gonna create that That tongue tap interface for you and it's gonna set up all those sort of buffers We need and basically empty connection state and Then I guess this is gonna be pub and then we're gonna have a pub fn listen So this is basically the same as So TCP listener, you can just call new Is there a bind I guess and it's sort of implicitly does the right thing Whereas for us We're gonna have We're also gonna have a bind, but it's just it's gonna take a mute self and a port It's gonna be a u16 and It's gonna give you back a TCP listener And we don't actually know what this It's gonna be yet, but we sort of know that that it's going to do something that means that now we're gonna start Answering sins on this port right Great And there are some other things that might carry over from main in fact most of this loop is gonna still remain here So you might already see where this gets tricky, right? When you get a TCP listener that TCP listener is going to have to to do things like Interact with the nick right the nick is where you read packets from or where you write right the packets to but if we Stuck this mute self which is a mutable reference to the nick into the TCP listener That would mean that you could only have one TCP listener open at any given point in time, which seems kind of problematic There are a couple of ways we can get around this One of them would be that we just store like An arc mutex interface inside everything we give out and then they just sort of multiplex between them That's also not great though because it would mean that imagine you have two TCP streams on the same nick and they both try to take the mutex on interface and then Imagine you have TCP stream one and TCP TCP stream two TCP stream one gets the mutex and reads a packet, but that packet is for TCP stream two It wouldn't have a way to tell the other stream. Oh, I found a packet for you, right? It just has to read until it gets its own packet And so We need to find a way to get around that particular issue one way we might do that is to Is to have the TCP streams Really just be like almost channels to some central thread. It's a little sad because it means that We basically have to copy all the bytes like if you write out these bytes We're actually gonna have to copy them into a vector somewhere and then send them off to this other thread Which isn't exactly great Yeah, I don't have a good answer there I think one way to do this would be If you want to write you take the mutex and you call into the event loop until the stuff that you want to write has been written But that has its own set of problems Okay, let's go with the channel approach for now and then we can Try to figure out if we can optimize this somehow Somewhere in the future So what that means is When you call new what we're really going to do is we're gonna Spin up a thread that's gonna run in the background and manage all the packet stuff And then we're just gonna return a handle to that so here instead of just having interface We're gonna have like Oh Actually that I Sort of want the public struct to be called interface, but all this is really going to contain is a Sync MPSC Something right so this is gonna be just an MPSC Sender Yeah, we're gonna run into a sort of weird pattern here where this is actually gonna be Here Interface requests and that is either going to be a right in which case it's a Vek U8 Or it's gonna be a read Which is then gonna include a Sender of a Vek U8 and I guess flush here as well And I wonder whether all of these Yeah, so the trick is imagine that the idea here is basically if you try to write on a TCP stream What you're really going to do is get you're gonna send a right message Over the channel to the thread that actually runs the interface And then it's gonna do like it's gonna keep running its event loop and at some point it decides That your right is done, but at that point it has to communicate the return value of that right back to you. I Mean that is really awkward. We sort of don't really want that to be how it works Because what that would imply is that the writer if you do a right call You also include like a channel that the server is gonna respond to you on I guess we could do that. It just It feels a little awkward Hmm The other reason this is unfortunate is just like it adds a lot of overhead to reads and writes and I feel like we could do better but Let's stick with it for now and then we could always improve it later So all of these are gonna have channel to sort of a Actually, this is gonna be a u-size. So this is gonna be how many bytes were written. This is gonna return all the bytes read And in fact, this is also gonna have to include a u-size which is the Capacity of the buffer so that we don't try to read more bytes than we can I was gonna send back at most that many bytes Okay, so interface then is really just a Thing that can send those requests to the interface main event loop and this I guess is gonna be What are we gonna call this? It's not gonna be public. It's gonna be the Connection manager And so at that point the see it we're gonna when you do new We're gonna create one of these connection managers Then we're gonna do a thread spawn I guess we're gonna do a We're gonna create a send and a receive handle for an MPSC channel We're gonna move the receiving end into that thread and that's gonna be CM dot run on Rx and I guess we can keep track of the join handle for when we drop at the end and Then this is gonna return then an interface Let's make this a little nicer for ourselves That has a transmit handle for where you send the requests and also a join handle That's gonna let you That's gonna let you join with the connection manager when you want it to exit at the end It's gonna be a join handle, and I don't know what that type is yet. So it's gonna be a whole So this now is gonna have Rx Tx and join handle There's the basic pattern here makes sense like the rough setup for what we're trying to do And I guess we have to do is also have to be unbind and It's also gonna have to take one of these and a port So I guess we can make these a A Little bit more helpful for ourselves Specifically a right is gonna be this is gonna be the bytes and this is gonna be the act channel So this is not a TCP act, but like a an act in the sense of I have completed your right a flush Just has an act field Bind as a port in an act unbind So unbind is what we have to do when you drop a TCP listener Right when you drop a TCP listener We have to make sure that we're no longer bound to that port But there we don't actually have to wait for the unbind to finish We can probably just unbind sort of asynchronously. That's not really a problem and a read is max length and red alright So we're gonna spawn this connection manager on its own thread. It's gonna do this run on which we haven't written yet So here there's gonna be an impulse connection manager This is gonna be an fn run on and it's gonna take a sender of these interface requests It's also gonna consume self and so this is gonna be the main event loop for packet processing Right and really all this is gonna do is This is an RX. So it's gonna do like for Request in RX And then it's gonna have to figure out what it actually does with each of those requests right and now Implementing these methods actually become relatively straightforward. So a TCP stream at that point is really just a An MPSC in fact we could probably use a type alias here so An interface handle is gonna be an MPC sender interface request So this here is an interface Handle and a TCP stream is really just an interface handle at this point And this is a little sad right so in theory you could imagine that we encapsulate the state associated with each stream Inside the TCP stream object itself That would be a very nice sort of clean separation of concerns rather than having the this sort of connection manager That just stores all the state in a giant hash map And so one day we might get to that for the time being I think we're stick to something. That's relatively simple And so this also is really just a an interface handle So when you bind what you're really going to do is you're gonna do self dot TX dot Send and you're gonna send an interface request And specifically you're gonna send a bind request on that port and you also need an act so we're gonna do Back in our X is a new channel And this here What we get back from bind is probably nothing right it's just a message that says I am now gonna start accepting packets So once we get that back We really just want to do sort of RX dot receive dot unwrap. We don't care what the value is Yes, and then we want to return okay TCP listener and we give it a copy of the of the transmit handle Right, I haven't said anything about what we do when we receive a bind But this is sort of the client side of this and you'll see that all the other methods end up looking very much the same So a read is really you send a read request The max length is going to be buff dot Capacity actually buff dot Len And What do you call it? We call that read and Here we actually care about the the response. So the response is going to be a bunch of bytes and Here it might be that these unwraps are actually not reasonable because you could imagine that you try to receive but the other side Like sent as a reset or something like a connection error happened So it might be that this eventually is not going to be an unwrap anymore, but for the time being let's leave it as an unwrap So here we now have a bunch of bytes and what we're going to do is we're going to do What's the slice copy thing? Copy isn't there a better one copy We'll do copy from slice seems fine So crucially we're going to do buff dot copy from slice Bites Right, so that's gonna copy all the as many bytes as we can from bytes into buff Does that return something no? and then we're going to return okay, and Wait this Response Right bytes dot land So that's the number of bytes that actually got read And here you could even imagine we do something like assert Bytes dot land is less than buff dot land Right if we ask the server to read at most buff dot land There's sort of the connection manager to read at most that many bytes then it shouldn't be returning more bytes than that Less than or equal I guess Similarly now, of course, I assume you can start to see the pattern right is going to be the same thing except this is now act and This is bytes and that's going to be back from buff Why is my linter not working This is the same And at some point we'll get back Message about how many bytes were written right so the The response for right the act sends a u-size which is the number of bytes that were actually written out It might not be all of bytes and Here we can sort of assert that n is going to be less than equal to buff land It can't have written out more bytes than we passed it And then we do okay, and that's how many bytes were written Can there be an issue on receive? Oh, yeah, absolutely so specifically You could imagine the connection gets closed. I'll say this needs to be a right You could imagine that you try to read but the connection was closed And so therefore the connection manager just closes your channel instead of sending you something and so this this unwrap is Definitely not okay. This is just to to get sort of the basic flow working Flush is basically a right but with no data So here this is a flush Flush only has an act and nothing else We don't care about its return value and we just return okay The last question here is accept so We need to figure out how accept is going to work Specifically, I think one thing that's missing here is currently there's no way for the connection manager when it gets one of these To know which connection you're trying to operate on so both right flush Read Yeah, right flush and read all need to include some kind of identifier for the underlying stream mmm, and I think the way we're gonna do that is Huh well we can use the quad so we have this the sort of quad identifier, right? And You can imagine that every time you want to do a right you have to sort of say which quad you're operating on So a flush and a read And this also means that if you try to do an accept Then what you get back is a quad right This probably needs to say which work So if you want to accept then if all the connection manager sees as you wanted to accept It doesn't know which of the ports to try to accept on And so We need to include that information and what you get back is a full identifier for that connection that you can then use for read flush and write This then means that the TCP stream needs to include a quad So that it knows which TCP stream it's for and TCP listener needs to include a port Which is gonna be a u-16 This also means that Down here This is gonna include the port and When you call accept on a TCP listener, let's move that code up a little When you call accept it's very similar to doing like a read or something except that Except that what you're issuing is an except You give the port which is the port that is This should be one Now all of these in fact should be one Yes, you send an except you include the port and you I guess And what you get back when you read from that channel Is you get back a quad that identifies the stream that was accepted and then you return a TCP stream that's gonna be that quad and a clone of the Interface handle so that it itself can send more requests And of course this whenever you send something you have to include the quad So the way this API is currently structured is like definitely not high performance, right? We're we're copying all the rights when you're doing a read or a write We're like allocating an extra buffer copying between them. We have to do all these like interface sends these Channel sends and receives every time you want to in fact two of them right one to send the request and want to send the response Every time you want to do any operation We have to like include this quad with every operation we do so there's definitely a bunch of Overheads here that we might one might hope that we could eventually get rid of but notice that the external API It doesn't actually reflect any of that right the internal API is just there is an interface, right? you have to create one and Once you have an interface you can bind you get a TCP listener You don't get to look at the fields inside the TCP listener all you know is you have one of these It has an accept method that returns a TCP stream and TCP stream implements read and write So the internal the external interface is still what we would have almost no matter what the internals were like And so we can always later optimize this to get away from this single event loop Alright, so the question now becomes what do we do in the main loop of the connection manager? Well We basically have to do what we did in main right we need to look for packets and such and I think specifically what we have to do is We sort of need to We're gonna have that this the sort of main event loop is gonna have to deal with a bunch of different things specifically It's gonna have to deal with packets coming in over the network Requests coming in over the interface. So these are like interface requests the ones we just wrote And it's going to have to deal with timers firing And so all of that is gonna be sort of stuff that it has to look at It'll be kind of cool to do this in async Because like async is just perfectly suited for this Let's think about that for a second what that is worthwhile I Really depends on whether a ton tap is async Because if it's not it's not gonna be worth it. Oh Async so it does support async That's pretty cool Async was it was very minimal and probably inefficient It's kind of tempting not gonna lie so the reason I think it's tempting is because We're basically writing an event loop here that needs to wait on multiple different types of things and doing that in async It's just a lot easier But if there's something like a try receive There is not hmm so the the problem we're gonna run into is I Imagine that you sort of There are no current requests coming from the user And so you so you have to try to read from the network to see if anything is happening there If you don't have a try receive then you're gonna do a receive and then it's gonna block until there's another packet But that could block for forever. It might be in the other side doesn't send any packets and now There might be a bunch of requests building up on the sender You're not gonna get to because you're still blocked on reading the next packet Which is problematic, right? Hmm So the real way to do this So the the most straightforward way to turn a synchronous interface like this into an asynchronous one is you spawn a thread and it has a channel And that's really sad because it's gonna call me more bites so the Okay, so here, let me try to draw this might be easier So the situation we're running into is as follows So the kernel is down here, right and it has like our network interface with packets coming in here Uh currently we have our connection manager here And we have a bunch of like TCP streams and TCP listeners and whatnot up here Currently all of these have sort of a a shared channel Right, that's the channel. We just created into the connection manager Right the connection manager also has a single connection down here Uh the problem we face is what does the main loop of the connection manager look like? Wow, it's supposed to be a arrow Right, what does this look like? Because it can't it can't block on reading from this because then it wouldn't read from this It can't block on reading from this because then it would block on this then it wouldn't read from this so one way in which this can We can sort of Fix this is that we introduce a separate thread over here and It's gonna have that connection. So we're not gonna have this one. It's gonna be the Packet manager if you will the package manager has a channel to here so to be clear This is an MPSC This is a ton tap Right and in the in the orange world This is a ton tap and this is an MPSC So multi-producer single consumer channel The reason this is The reason this works is because on channels Well the standard library channels you can't do what's known as a select so you can't arbitrarily select between the two but at least you can do like a try receive on this one and you can do a try receive on this one Right, and so that way you can essentially multiplex between them Probably what we want is to use cross beam channel There's a really cool crate that that may or may not end up in the standard library because it has this thing called a select So select lets you say You can sort of select between Channel one and channel two and this will block until either of them returns Right, and that fits really well into this model We're just gonna select between these two and whichever return first we handle and in fact we could do this with a timeout And that lets us also deal with timers firing The way in which this is inefficient though is imagine you're trying to do a read So the read is gonna progress as follows Let's say that this guy wants to do a read Okay, so they send a read request to here to the connection manager the connection manager Doesn't currently have any data But at some point or some bytes come in here Right, so this read some bytes So this now has like a bunch of bytes in a buffer somewhere it has to send it on Yeah, yeah, selects are great. So cross beam channel also has those selects the The package manager the packet manager Now has to send this buffer to the connection manager But this is on the stack right the bytes we read here on the stack So it needs to actually copy them over here So it copies them over to to this to the stuff that it sends to this channel the connection manager then Gets those bytes has to do a bunch of parsing right has to run all the protocol stuff So it internally has like a buffer of unread data for every connection And these bytes are just going to be extended to that buffer And now it needs to respond to this read So it's going to take some part of that buffer There's going to be the next bytes to read it has to copy those into a new buffer That it can then send over this channel Right, so notice how many extra both Channel sends and memory copies and allocations we end up doing in the scheme It's really not great. It would work like it would it would mean that you end up with something that works But it is pretty unfortunate Trying to think if there's a good way to Design this differently, I mean there probably is but it requires some design Um So ultimately what would have been the the sort of best possible design if we could figure out how to do it Okay, so let's uh, let's start another sort of hypothetical design somewhere over here Uh So we're going to have a channel Sorry, we're going to have the kernel this has to the network interface Uh, we will still have a bunch of tcp streams Uh, that logically sort of get they get sort of multiplexed over this one connection This one network interface, right? That's sort of the core of our problem is that these have to Collectively drive the network interface forward Um And each one has a bunch of connection state That's not the best way to draw that so this is the connection state for each one uh If we want to Um Yeah, so each of them has their own private Private state that's things like the sequence number And the act number and the window size and whatnot so each of them has one of those um And now imagine that you're trying to do a read from here So this guy Trying to read If this reads it's going to have to ultimately like look at this channel We have to somehow get data from this to this The problem is there's a bunch of packets here, right? Like who knows what kind of packets you're going to get from this interface and like this might be for this one This might be for this one. This might be for this one And this maybe like this one is actually the one that goes here To the one that tries to read and You still need to decide what you're doing with all the intervening packets um You sort of want to process them, right? You you can't drop or you could drop them, but that's that's really inefficient. Um you can Sort of stash them for later. So you could keep sort of a a queue So imagine that this takes a lock, right? Imagine like there's a giant lock that's like protecting all of this Lock Then there could be a giant queue Where any packet that you encounter that's not for you you stick it into that queue And so and so when you get to yours then you process that one And any subsequent thing that comes in to To look for stuff is going to first go through the queue and see if it finds things that's for it um So the question is isn't what you're describing basically network specific work stealing um, it's well Not quite so the the way in which it's different is that here Um, it's not as though we have multiple network connections um We actually We have a single So logically we have a single incoming network connection Usually when you have work stealing because the kernel The kernel gives you this impression that you actually have multiple separate streams, right? It doesn't expose a single network interface to your application You think that you have multiple sort of separate streams Um, you have one thread that or you have a bunch of threads that are managing That's a terrible way to draw it. You have a bunch of threads up here um And these threads are just going to like pick up packets from wherever they get them from the kernel and sometimes The package you read needs to be handled by some particular thread and so they steal things among themselves Um, but at no point here is there really like a single point you're going through um The other thing it would be a nice bonus is if we Didn't have to do this if we didn't have to stash these packets away because these packets If we have to stash them away, we have to stick them basically in some memory That's not going to go away So we're going to have to create like a veq u8 for each one And store those it'd be great if if this If this node notices this packet for this uh peer It could just sort of do the processing For that node, but that means that this state has to be in here I mean, maybe that's okay. Maybe this actually keeps So inside the lock you also keep the state for every connection um It still means that any bytes you read here you're going to have to stash for later because um if uh If Let's see. Okay, so let's call this the red the red connection and this packet is for the red connection and let's call this the uh This is the yellow connection and this packet is for the yellow connection so the Red connection is going to end up seeing the yellow packet before it sees the red packet um and Um and the problem is the memory in yellow Ideally, we would just sort of give to yellow so that so that we don't have to copy the bytes anywhere But maybe that's not that important. Why not have node specific inboxes that you can stitch memory into stick memory into Yeah, so that's basically the proposal here is that we have a sort of queue of things that we haven't processed yet Uh, maybe instead what we should do I should not have painted that yellow Dark blue dark blue This is a terrible color to read in this um So maybe what we should do instead is keep all the state for every connection in this sort of one map So the inside the lock you have all the connection state and So like we would have we would end up with like a red connection state and a yellow connection state Uh and whatever other connection states we have and this would be sort of all of the bytes That have been red That we have gotten packets for on red, but that red has not Red itself and similarly with yellow Okay, so maybe that's a better design That any time Okay, so the problem with this is uh, imagine that red tries to read And it just doesn't get any packets for itself, right? So this uh, this is why I didn't do this in the first place. Um so Red is trying to read so red takes the lock At this point when red has the lock It starts to walk through these packets, right? Imagine that there is no red packet here There's no red packet And so red is just going to keep reading these packets and it still holds the lock So no one else can read even though there might be a bunch of data for yellow a bunch of data for these blue nodes, right? Like all of the other connections have gotten bunch of data, but red still hasn't gotten their packet So there needs to be a way for red to sort of yield, right? Red has to at some point decide i'm gonna let other people go now Um, and I don't quite know how we're gonna do that um Um, so this is why in the in the other design This is sort of a non problem, right? Because the connection manager just sort of notifies people as appropriate Um It just keeps reading packets and if it gets something for red it would notify red if it gets something for yellow It would notify yellow Whereas here we don't really have that same luxury you can imagine that red Sort of parses a packet and the first packet it sees it's going to yield So that someone else can read it And then it might have to take the lock again um Yeah, it's just going to give up the lock and then try to take the lock again immediately Basically, it's going to read. Let's imagine this packet isn't here. So it it reads the yellow packet Um notices that it isn't for itself And so it just gives up the lock and notifies yellow which is presumably what waiting for this This is going to be we're probably going to end up using something like a con bar here So a con bar is a way to wake up It's a conditional variable is the way to think about it. So yellow if it tries to read It's going to see if it reads if it fails It's going to wait on its own con bar, which includes releasing the lock Red is going to notice that yellow wants to be woken up It's going to notify yellow and is then going to probably yield and then immediately try to take the lock again Because it hasn't satisfied its read yet It's going to be a lot of contention on this lock because this lock guards the entire connection state But it might be a better design. It means that we don't need to have all these threads running around And so that might actually be better, but it is definitely a little sad We also run into a weird case of timers Because the timers need to run here too somewhere Although maybe we could have a separate timer thread That also takes this lock So this is going to be like a timer thing And the timer is going to do things like Decide whether it's time to try to retry sending a packet or whatnot And it's also going to have to take the lock and all of this remember is an artifact of The fact that we only have one underlying connection And you can think of this as internally inside of the kernel, right? It's pretending that it has all these channels Right so every if you do normal network programming you just have separate tcp streams And you don't really think about the fact that they're connected somehow But really sort of under under the kernel there really is just a single network interface It's like one ethernet port or one wi-fi card And somehow all of these have to be funneled through that one Interface and the kernel takes care of that and it takes care of that without you really seeing what's going on And you can imagine that the kernel has to do something sort of similar to this They might not use a lock. They might there might be some like smart lock-free data structure You can use here so that you don't have to take this lock The kernel can sort of always have some threads running that don't need to context switch But but this is like a fundamental problem that we now have to solve in user space A Color a Read packets until another one b comes up and then b reads its packets until yeah, so the basic idea here is going to be You only ever read one packet at a time So red reads a packet and we could make it more than Maybe This still becomes problematic Okay, so the the idea was Red reads one packet and if that packet is for red all good. We might be done If it's not for red it's going to notify Whichever thing it was for And then it's going to sort of wait for a little while and then it's going to take the lock again It's basically going to yield and then then then take the lock Which is some overhead But probably less overhead than all these channels and certainly less sort of byte copying I just realized though that one other problem we run into here is the Imagine that you're red and you try to read a packet And that blocks because there are no packets and the reason there are no packets is because we need to do a retry And so therefore the timer has to fire but the timer needs to take the lock in order to send anything So But it can't take the lock because red is holding the lock blocking for a packet I think what this means is Yeah, I don't know how we do that Oh, here's a possibility Okay, how about this design uh, how about If any tcp stream i'm running out of colors orange orange is a color great, uh Anytime a tcp stream tries to access this it is not allowed to try to read a packet cannot read basically sort of From the nick From the from this Right, it's not allowed to read anything from here. It's not allowed to make sort of a blocking call to get more data Instead if it sort of looks in its buffers or sticks some stuff in its buffers and It will And if it fails to do that if it can't actually read anything then it's going to get one of these con bars is going to wait on Stuff being available and it's slot if you will right And then we're going to have One thread that's going to be running on the side Um, and that thread is going to deal both with timers and it's going to be the thing that reads So it is going to In fact might even be the thing that just owns the Ah, okay here. How about this? So the the sort of severs the connection between here we're going to Have this thread own the nick and instead of having this be a channel like we this is sort of a merge between these two designs We're going to have this thread that's going to be the thing that reads from the nick And whenever it gets a packet from the nick it is going to take this lock Do the processing and put stuff in the appropriate place and wake threads up So now we've sort of disconnected these two um Uh But the mutex basically lets us not have to copy all the data around And because this thread all it's doing is reading from the the nick it doesn't matter that it's blocking Eh, oh, but it still needs to be with timers I think timers are going to be a pain here um But I kind of like this design uh, I think timers The problem with timers is they have to do things with the nick Right, they have to they may have to send a packet or something, right? um But if this thread is blocked on trying to receive a packet you can't like you can't do this Which is why This sort of needs a receive timeout function I don't know if there's a way to do that though Oh, it might be it might be that the way we do this It's going to be really funky, but um Um Basically we can uh get a raw file descriptor to the underlying thing Oh, that allows for all sorts of cheating, but basically we can um Just before we make this call to block Right, uh, sorry. This is called read right read blocks Um, and if we wanted to block for at most a certain amount of time this um, this Crate doesn't let us do that, right? It only has a receive method. Um, but Man read Now man to read um But Where's the timeout business? uh So there's a way to set Uh Sock got Sock Oh Maybe he's just socket No, what's the thing for setting? Um Actually, we can do this in a different way if we look at the rust docs for received timeout So on a tcp stream you have received timeout. What does that do internally? Well, that's platform specific that I think we need to go to the real code So let's see Um lib lib standard sys unix Or is net Here So let's look for receive Or is it read timeout? Maybe it's just common then net Yeah, so there's this So basically there's a way that we can um Because we have this Uh Because we have this as as raw fd and into raw fd We can get sort of a raw handle to the underlying thing And then we might be able to just set a timeout directly on it so that the read call actually will time out um But in order to do that we would have to Find Set timeout, okay And where does that come from here? Set sock off, right So we can basically do the same thing that rust internally does to set a timeout on a particular channel Because all this is doing All receive is doing is really just calling read on the underlying file descriptor And so what we're going to do is we're going to set the same We're going to set a timeout on this file descriptor so that a read will block for it most so and so long And so this basically gives us a way to emulate receive timeout Uh, and we'll see whether that works And so that would mean that um when when this thread that manages sending and receiving packets does a read It's actually going to do a read timeout Uh To That's going to be sort of At most the time until the next timer fires Now we're probably not going to end up implementing timers in this particular stream Or like it might happen in a future tcp stream But we need to make sure that the design actually supports having timers because they're critical to getting tcp to work Okay, so I think what this now suggests is This is somewhat similar to the design we had before But with fewer threads and fewer channels So instead what we're going to do is we're going to have every tcp stream is really just going to have a pointer to a mutex Of all the sort of connection state. This is basically what we had in connection manager before In connection manager is going to have everything except for the nick itself So this is going to contain nick This is going to contain connections Right if we think about the the data structure And then the thread that reads from the nick is going to populate things into the connection structure by managing the connections All right, so let's give that a try Uh, it means we have to rejig this a little bit because we no longer need these interface requests Which is good, right? Like getting rid of that seems fantastic So What's really now going to happen is when you create a new interface Uh, we're going to create the nick Right, so that we have to create regardless Um, we're going to spawn We're going to spawn some thread that's going to sort of manage the nick um, and we're going to have I guess a Interface handle And an interface handle is really just going to be it's going to be a type That's going to be an arc mutex Uh connection Manager Probably does not need buff either Instead Um Here So this thread is going to be what holds the nick It's going to have a buff It's going to be this And it's going to Do the stuff that main does But anytime it has to touch a connection it actually has to take the lock um So up here we first have to The We have to create the sort of connection struct that everyone's going to look at so we're going to connection manager default This can Presumably just derive default Uh, and in fact this is going to be an arc New mutex new connection manager default We might even be able to just say arc default here Uh But type inference is going to come kicker us. Oh, actually we can do cm interface handle Is arc default Great All right, so, uh the This extra thread that we've made it's going to need a copy of the connection manager, right It's not currently doing anything but it's going to have to do that at some point um And the interface now is really just this handle to the connection manager So interface is a interface handle And a join handle So the interface handle is where Uh Is where all the stuff for all the connections is so that's where you're going to have to lock to do reads and writes Or accepts for that matter um This is no longer a thing Bind now so bind is really just going to do um It's going to do self dot Interface handle dot lock And I guess unwrap right so it has to lock the interface It has to then do uh something to uh Start accepting syn packets On the given port right so that's going to modify some data structure in cm that we haven't constructed yet um And then it's going to drop cm And then it's going to return This Right so this is releasing the lock And then it returns the tcp listener with the port because it needs to be able to identify the connection later And the the interface handle because it needs to be able to lock in order to accept packets Uh So that's tcp listener. So if it wants to accept what does it do? Well, it has to take the lock um It then has to see whether there are any Sort of pending connections for its port um So the way it's going to do that is um It might be what we can do finer grain locking here, but let's ignore that for a second So cm is definitely going to have something here like um Like pending It's going to be a hash map from port to what to a Quad vex quad Yeah so, uh So pending here is a list of pending connections to this port Right, so if there are no pending connections this vex is empty if you are not listening then There will be no entry for that port And so now we know what bind has to do bind has to do self dot pending dot insert port back to you Um And we're going to hear assert that uh was bound as none Right, so there wasn't already someone bound to this port Or the way we can do this in the sort of better way is collections Hash map entry and then we match on uh We match on Entry report, uh, and if it was if it is vacant Um, then we insert a vac new Uh And return okay um if it is Occupied Uh, then we return an error Right the this means the user tried to bind to a port that it has already been bound to Uh, and then we return error new error kind I can never remember what the error kinds are Uh add or in use port already Bound like so and this should be cm Does that make sense? So, uh for bind we take the lock, uh, we Add an entry to pending for our port to say that we're willing to accept connections Initially it has no new connections Um, and if it was already bound like if there was already an entry for that port Then we return an error saying we can't bound to a port that's already been bound Okay, so the question now becomes what does accept to well Accept is really just going to be um It's going to look in pending so It's going to do cm dot get mute port self dot zero I guess we could be nice and name these but for now we won't um, and we're going to say If let some quad is get mute This we actually expect to be some because we already have a tcp listener open um So this is something like Uh Port closed while listeners still active Uh And then we want to pop this is from the veck Actually, I guess this should probably be a veck dq collections veck dq and hash map um So specifically we want to do a You can imagine that this is going to be new connections are going to be pushed to the back And we're going to pop connections of the front And so if there is a connection for us to pop, uh, then we're going to just return uh A tcp stream for that quad Otherwise we actually have to block right so here Here we've been told to accept the connection, but there is no connection to accept And so here what we really want to do is block. Um To do block So for now we're going to do this we're going to have a try accept Uh, that is going to do return error i o Error new i o error kind Uh, tried to do Uh, no connection available To accept Right, we probably want an accept function that will actually block. It's just that That's going to require us to write some infrastructure that we haven't done yet I just want to walk through these first and then we can add all the There's sort of necessary bits and pieces afterwards for for doing actual blocking Um this before we continue does this design roughly make sense Right like what we ended up on here. Why we ended up on that design Uh, what the sort of challenges of it was and what the general approach we're now going for is Um, I sort of want to hear from chat that we Uh that I haven't sort of lost you along the way It's easy when writing this to sort of um Like I have a stack internally in my head of what we're doing But if that doesn't match your stack then I've sort of lost you along the way and that would be unfortunate So let's try to re-sync just to check that people are still understanding roughly what what's going on all right Sensei clo clo says yes, it does great. So we have one person on board There are in theory, uh 51 people in chat What do the other 50 people think or some subset of them? Maybe everyone has me muted You're e too busy eating pizza. That sounds pretty great. I wish I was eating pizza I'm gonna assume that if you're eating pizza, you're understanding everything that's going on pizza clearly just like Makes your mind aware Right, that's how pizza works All right Well, there doesn't seem to be any loud complaints. So we're gonna Continue and feel free to stop me if something does not make sense Because remember if it doesn't if you think it doesn't make sense to you It is probably the case that it also doesn't make sense to other people Like whatever questions you have in your head where enough people here There are probably other people with those same questions So please feel free to interrupt if you feel like something isn't uh isn't adding up Um, all right. So for reading at this point the pattern should start to become clear Uh, we're gonna lock Then we're gonna have to see whether there's data available for this connection So here too, uh The connection is going to be self dot no cm dot Connections see this is going to be cm dot pending Uh, this is going to be cm dot connections dot get mute self dot zero This is going to be a reference a reference Here too, we basically expect there to be a thing because we have a tcp stream So the tcp stream should still be there. It might be that here You might get connection loss actually So let's do an okay or Uh else Um Error new IO error kind Not connected broken pipe. Oh, I think it's broken connection aborted. It seems good Uh tp stream was pretty much sure was Terminated unexpectedly Also, why is this not formatting my code? 87 Great formatting nice Okay, so, uh, this here is the connection Right, so this is the connection manager. The connection is just getting access to the connection object for this particular connection And now we want to do something similar to what we did for accept, which is uh, if um If So this is where it gets tricky because we haven't actually implemented data yet. Um, but if you look at tcp connection Here, there's just definitely going to be a like a receive buffer This is sort of incoming It's probably gonna be a Vic DQ Uh of u8 So this is going to be, um Bites that we have read from this connection, but that the user has not yet read. This is like a sort of buffer space Um, there's also going to be an outgoing buffer. So this is Bites that the user has Given us but we have not been able or that we have not been able to send yet And that could be that we've sent it in a packet, but the other side hasn't acted yet In fact, that's probably what it's going to be. This is going to be sort of unact I guess Um, okay, so here, uh, what we really want to do is if, uh C dot incoming Is empty Then, uh, to do we're going to need to block Right And we don't know how to do that yet So for now, uh No bytes to read So here really what we want to do is block Um, if we get down here though, it means that there is data there And so what we're actually going to do is just read however much data we can up to the size of buff Um And so here i'm going to have to look up DQ So there's a I want It's never the expansion. I want this Um There is a ass slices. That's what I want So a veck dq just for context is veck dq is a great little tool to have in your toolbox Um, so a veck dq is a ring buffer And if you don't know what a ring buffer is you're about to learn So A veck Looks like this, right? So this is zero And this is n where n is the length of your vector And if you push onto the vector you push over here right In reality, it's a little bit more complicated that than that because Uh, a vector in rust actually has I need to find a better way to erase Like this is just silly Um, I want when I switch to a race. I want a larger brush So really Uh, the picture is more like this isn't n. This is Capacity Uh, and this is len And all the stuff in between here is like stuff that you've written And if you push what really happens is you push some some stuff from here to here and now Let's call this len prime. So this is now the updated len But no allocation had to happen because the vector can actually hold this much data, right? So until once you get here if you then try to push More data then the vector is going to grow Now this is really neat. Um, the problem of course is imagine that you want to use this sort of like a q Right like imagine that You keep pushing stuff to the back, but I want to read stuff from the front If I try to read let's do Orange, uh, if I want to read like this bit That becomes really annoying in a vector because if I read this this stuff. This is now free space Right So I could move zero to here But if I kept doing that then eventually I would just like all all this stuff is still in memory The vector is still that long And so instead what I have to do is if I read this I have to take everything that follows it And sort of shifted to the left to fill in the space that was voided here. Otherwise, I never recover that space A vec dq tries to solve this problem by That one So a vec dq is somewhat similar in that it too Has a capacity However It has instead of having just sort of everything starts at zero It has a head and a length sorry a head and a tail So initially The head points to zero And the tail points to zero Uh, if the head and the tail are equal then the thing is empty If you push what really happens is So this is a push then the head starts pointing here and it no longer points to here If you push again the head now points to here It doesn't point to here anymore. The tail still points here Uh, I might have gone head and tail messed up here. This is the tail This is the head So head does in the start of tail us in the end of so the start of the data is here So this is the head not the tail. This is the end of the data. So this is the tail not the head And so I might keep pushing stuff and eventually I'm going to end up in a position This is to ignore all of this at this point where the tail points here And the head still points to the start So the question is now what happens Oh, that's a terrible color Now what happens if I want to read this thing? Well reading this is now trivial because all I have to do is increment the head And any indexing operation just always starts from the head So if the head is now here this now has length four Because the length is from the head to the tail always If I push I still push the tail if I pop if I Pop from the front then I just increment the head right Where vekdqs get a little funky is as you move further to the right You can easily end up in situations like this one where You have your vekdq and the Where the so this is still the capacity And the tail currently points to here Right and the head points here Right And there's still a bunch of stuff in between here that I've like written to in the past But currently is not being used by anything So the head is currently pointing here and let's say that there are three elements here So these are currently the three elements of the vekdq So if I popped three times I would get three these three things But now what happens if I push well, what actually happens when I push is the tail wraps around And moves to here So tail is now going to point to over here. It's not going to point to here anymore And now this is an element And so notice that what this means is the vekdq can sort of become bifurcated right It now it's now no longer a single region of memory The vekdq if you were to read it start to finish you would first read these bytes and then you would read these bytes And so this is why In the vekdq case over here You can't deref into a u8 into a sorry into a slice you can only dereference into two slices Which is the leading end and the tail end Or in other words, it is this bit And then this bit If you push data beyond the allocated size So Basically the same thing happens as in a vector Uh, let's go with this color So Imagine that we've gotten to the point where the the tail is here Right well the tail would never technically be there But imagine that the the like the thing is full and you try to push it would detect that you're trying to push And that the capacity has been reached So what we it would do is it would do the same thing as you do with a vector It allocates a new vekdq that is twice as long Imagine that that is twice as long then it takes all of the existing things And sticks them into here and of course is the way it's going to do that It's going to take these things and stick them here and then these things because it's full right and stick them towards the end And so now you have all the stuff in order again And now it's going to set the new The new head Sorry, the new head to be here and the new tail to be here And now you can push by pushing to the tail So It is basically the same cost as a vector the only way in which it's different is in a vector If you need to resize the vector you can copy a single segment of memory Whereas with a vekdq you have to copy two segments of memory But the total size is the same I doubt that you would notice this in practice except if you were doing lots of resizes But in general the because of the way this resizing works if sort of doubling each time the We call this an amortized cost that you would expect to not see resizes as your data grows Because they become rarer and rarer the more data you have So effectively they're not that different and I think Very often a vekdq is what you want if you want to be able to operate on both ends If you only ever operate on one end then a vek is perfectly fine And the vek has the nice Property that you can always get a slice to the entire contents. Whereas that is not true for a vekdq Um And so now when we do a read what we really have to do is do a like a head tail is Uh C dot incoming Dot ass slices Actually, I might be able to do even better than that Gee, we might be able to just do drain and not have to I was gonna I was gonna manually use like the copy from slice manually for head and tail But I think instead what we'll do is Uh Actually, we might not be able I wonder if there's a This gives us an iterator over bytes, which is probably not what we want Wonder if there's a I mean we could the the problem is that we have a we have a buff where because these are These are u8s. It's not like this is a a vector. We're gonna push to it It's just we're gonna overwrite the bytes that are there. So I think we might actually still want this um, so if Uh Is there a way for me to I want like truncate start But I don't know if that's a thing here Blit off is definitely not what I want Rotate left. That's definitely not what I want Huh All right, what does drain do internally and that just walks that doesn't really help us Actually, let's see what drain does No, I want match you need the drain and give me the iterator for drain So when the iterator is dropped, how does it oh, it just directly changes the tail, doesn't it? Yeah, let's worry about that Hmm, I don't know how to best use this api at this point Because I want to not have to pop bytes individually Right, I got it. Okay. So here's here's one thing we could do. This is sort of stupid, but um uh actually for I See n red is zero while let This is I'm just going to show why I think this is stupid. Uh while at some byte is c dot incoming dot pop front n red plus equals one buff n red is equal to byte And I guess If it'd be a loop instead of that if uh n red Is equal to buff dot length then we break um Then we're gonna match on this And if it's some then we get a byte Then we do this And if it's none that means there are no more bytes in the incoming buffer Uh, so then we also break Right, so this will do the right thing, but it's really stupid We're copying one byte at a time. I want the equivalent of this code, but without copying one byte at a time um And I think the way we do that is uh Well, the problem is we need to be able to Remove stuff from the start I'm actually kind of surprised that there isn't a Um An efficient way to just change the head I mean maybe the trick is drain Actually, yeah, that's problem Uh, okay, so I think what we actually want to do here is um Headtail is c dot incoming dot uh slices Um, and then we're gonna do If Okay, so we know this is not empty So we're first going to copy bytes from the head Uh, let's see let mute and red is zero Uh So first We're going to do Buff copy from slice head Is buff dot one It's not quite work either Let me write out roughly what I was thinking Uh And then we can discuss Uh C dot incoming dot drain So I think that is basically equivalent Except that we need to make sure that we don't write more bytes into buff than buff can take Um, I think slicing Breaks I think we need um H read is going to be Compare min of buff dot len and head dot len And we're going to do h read and we're going to do n red plus equals h red tail read is going to be Uh buff dot len minus n red Uh and Tail dot len so this might be zero right it might be that we read all the bytes we needed Um But it means that we don't really have to branch here which like might be nice So this might read no bytes And then we drain as many bytes as we read And at this point we can do Okay And read And I'm going to leave it to do for myself here that might not make sense right now, but uh, we'll come back to it Actually, you know Great, okay, so, uh, let's take a look at read now that we have the whole thing Right, we first try to take the lock right because we need access to the the data That's been read. Um, we look up the connection for the tcp stream. We're trying to read from If there's no data, we're gonna have to block currently we don't know how to block And so instead we just return would block If there is data though, then we will read as much data as we can There's actually also a question here of Detect Fin and return n red Because we're gonna have to detect when the other side of stopped sending us data and then read the contract for read is basically You need to return okay zero if the other side is not going to send you anymore and currently we're not detecting that Um, and I don't think we have a way to detect that either at the moment Um, yeah Um, okay, so if there's no data, then we need to block and we don't know how to do that yet Otherwise, um, we're basically just going to read as much as we can from the incoming buffer That still fits within buff and then we're going to re return the number of bytes we ended up reading And of course, uh, right is going to end up being very very similar except going sort of the opposite direction Um, specifically We're going to have to take the lock we're going to have to look up the appropriate connection, uh, if Ooh That's also a good question So here's a question should write ever block Right, we could always keep doing writes by just expanding the buffer the sort of outgoing send buffer Um, but that might not be what we want. We might want to disallow more than a certain number of packets outstanding Right, so think of it this way. Um Um Go over here, uh, we're gonna switch to the nice blue so, um imagine sort of in the very abstract right a client And a server as a connection going this way and there's a connection going this way so this is after the handshake has happened, right and Uh in this setup Ooh The chat go away or is chat still there Suddenly the chat service is reporting zero people From like 60 which seems wrong Um, I'll keep going But if someone can type something they'll be helpful. Um, okay, so you have a client on the server and um Uh There's like the server is going to have some capacity like it's not going to let you send it just just keep spewing data to it It's going to say like Okay, great. Thanks. Um, so Imagine that it has a limit of like 1,024 bytes or something Like you are not allowed to send me more than this much before you hear back from me that I've received them Well, the client still has some application program that's running on it, right that's sending it data So imagine that this is like sending just like tons and tons of data way more and way faster than the servers accepting your data You have a question right either you need to block the application saying you're not allowed to send more data Or you need to buffer up more and more and more data in memory on the client That is like bytes that you have received you have accepted from the application But you have not sent out on the wire yet, right And so the question becomes which of these two approaches you want to do You probably want to block the application at some point But that basically just means that there has to be a limit to how Much how many bytes you buffer up in the send queue before You say no to the application and in fact um Usually on linux for example, um, you can find this out. So if you type, uh IP I thought it was ip link Qln linux Qln there's a command to get more information about this Yeah, so actually, uh It's kind of funny to see how what we're building is basically exactly what exists in the kernel But the kernel tries to be relatively clever on it. Like there are lots of people who are Doing research on how to manage this queue, right? um And specifically Ah, where's the piano show Where's the thing that gives me Qln? Well, that's entirely unhelpful it's like net tcp Qln All right, I can't find it but Let's ignore that but there's basically like it's not quite this parameter I think but there's like a There's a limit in the kernel to how many bytes you are allowed to Stick in the buffer before the kernel will start to block you Um, and so we have the same problem with write at one point. Do we see and say sort of enough is enough? um And that's basically what we're going to end up putting here, right if c dot outgoing dot length is more than like Uh Send queue size So we're going to have some const Send queue size it's going to be 1024 for the time being Um, then we're going to have to block Right Too many bytes buffered And we're going to have to block until some of the bytes that are in the queue have been sent out on the network Until the server has started basically accepting more of our bytes We're going to have to block the application Currently, we don't know how to do that. So currently we're just going to return woodblock, but this is the case in which we would block um Great, um, I also just realized that another thing we're going to want is import tcp stream Uh shutdown So this is the way in which you can send Uh, you can sort of tell the other side that you're not going to send any more data Uh, and this is a Like an ionet. Is that right? Standard net shutdown This is basically going to send a fin if you remember from last stream Uh Okay, so we're going to try to write out this many bytes into Um into this buffer And it's going to work pretty similarly to what we did in the In the uh in read except it's sort of in reverse instead of reading bytes out we're going to push bytes in um And allowed to write I guess n right is going to be Send q size minus c dot outgoing dot len That's how many bytes we're going to be allowed to write and it's going to be Uh standard min Of buff dot len and that much right so we will write Either as many bytes as we have or as many bytes as we're allowed to whichever is smaller Right because we can't write out more bytes than we've been given Uh q lengths with s s dash l No, that's the current size of the q. I was looking for the yeah, so You are right. So s s uh t l Uh t l p Oh What? That's oh t p n That's what I want Um, so this is showing me actually let's ignore the n Um and ignore the p So this is showing me all currently open tcp connections on my machine And for each one how many bytes are in the send buffer and how many bytes are in the receive buffer um and uh And also things like which tcp state we're in so this is kind of neat um But this doesn't show me the maximum buffer that the kernel enforces Still try to figure out how I would do a lock free circular ring buffer In rust this would be ideal in this situation. Um It wouldn't be quite enough because you still need the wake-ups Which you don't quite get with the lock free stuff It it gets you quite far but not quite enough. I think some finer grained locking might help us here um Okay, so that's how many bytes we're going to write and of course the Actually writing this is is pretty easy because we don't need to worry about the head and the tail Instead we are just going to do Uh c dot outgoing dot extend buff from Buff to and write And then we're gonna do oh I guess And then we're gonna do okay and right Because that's how many bytes we wrote up Um Here though we might have to do a to do schedule Wake up writer Uh And a flush So a flush is basically saying Uh block until there are no Um Until there are no bytes in the local buffer every byte has been act by the server Um, and so this is also going to work very similarly Except that it's going to be Uh If it's empty then we're just going to return okay Right if the outgoing buffer is empty then we're done Otherwise we're gonna have to block and we don't know how to block yet So we're gonna have to figure that out And shutdown is basically setting the fin flag which is not something we have an external interface for yet But now let's see how far we get in compiling this We need a bunch of things don't we It's gonna have These three We also still have to do all the stuff that main does to manage packets Um, oh, it's not standard minute standard compare min No field ih That is true Arguably there should be a field call that let's Make things nicer for ourselves. So this is the port And this is the interface handle Could arguably just be h And tcp stream is similarly going to be the Quad And it might be that we don't actually want this to be a quad It might be that we just want this to be a like a numeric index or something instead But for the time being it's a quad which I guess also means that it's going to be here And down here in flush and tcp listener is going to be H and this is going to be Port no It's not true. It's going to be h This is going to be port. All right, we're getting closer Uh 66 That is actually ih And this is actually ih Because we need to oops Um, we need to distinguish it from the join handle, which is also a handle In this instance Uh mismatch types on line 51 Bind Oh, this is not this is port and handle And I guess that also means that except should also be Uh tcp stream quad and handle How about now missing fields incoming Oh because we added fields to this So this is online 101 Um, so the incoming and outgoing buffers here are initially going to be empty Oh Did I not call it out go unact And I guess That also means that here there's no outgoing It is Unact Oh, yeah, we no longer need the npsc. That's nice Uh 48 that should be ih not cm What else do we have mismatch type line 50 bind Did I like do something silly yep mutex and arc that's fine Sync Mutex and arc 158 Iterator That's interesting. So for a vec dq What do they want us to do? Why don't want to push a bunch of stuff? Okay, so I may just have to do like Iter I guess Oh right We want to be able to access these fields So incoming and unact need to be pubcrate for now 56 Pending pending should be a vec dq 56 Should be new Uh pubcrate means that it is public to any code that is in the same crate As where you put pubcrate So it's not public to people who use your crate as a library for example, but it is public it is available within your crate Think of it as um Sort of like protected in java Maybe This is gonna have to be mute All right main is gonna have to change a lot that's fine Uh Bunch of complaints in tcp. That's fine. Those are sort of To be expected. We do want to fix the ones that are in source lib though Okay, I think those are all expected Great. So now time to fix main, right? So we still have this to do of um Something has to be reading from the nick and currently nothing is reading from the nick, right? So all the stuff that happens in main currently we want to do here And so this is going to be something along the lines of um Packet loop and it's going to be given a nick And the ih and so we're going to have a I guess We may end up splitting this file into multiple files and not too long The nick is going to be mutable and it's going to be a ton tap iface Uh, and the ih is an interface handle And we know that this is going to need to have a buffer of some kind to read the bytes into And that's going to be The buffer we all know and love Um and inside here, it's basically going to do this loop, right? It's going to receive some stuff It's going to parse the stuff But anytime it needs to actually touch the underlying state Uh, the status bar This is airline. I have a separate video on my youtube channel. That's how my entire editor is set up So you should go watch that if you're interested um So what we need this to do is whenever it's about to touch the underlying state It needs to try to take the lock, right? Because there could be someone else reading at the same time, for example um And that is basically down here. We're going to do, uh cm is ih dot lock dot unwrap And then we're going to do cm Dot connections dot entry If there is one, we're just going to do we're going to process the packet. Um If If it's vacant, then the question becomes, uh, is someone waiting, right? So here it now becomes a question of uh If let, um Some pending is sell no is, uh cm dot pending get mute Uh Tcph destination port Right, this is basically checking is anyone listening Do we have a listener for this port and only if that is true Do we go ahead and do this? Uh And then we both insert into e and we also do pending dot Push back because we now accepted that connection. So we also push the quad So let's go ahead and store the quad somewhere Let q is equal to this Then we push the q to indicate that this is now a connection that exists um And of course in this case to do, uh wake up pending accept, right? So here we just accepted a connection and it might be that's So currently nothing really blocks, but if an accept call is blocking waiting for something to appear in pending We need to wake that up and we haven't really said how we're going to do that yet Uh, the buffer math can all be in the circular buffer and it would return Woodblock only if there's not enough room in it and you can avoid all the locking part as well Yeah, the problem. So I agree with you. The problem is we will not actually be using woodblock We need to actually block and that's where you're going to need something. That's a synchronization mechanism for waking people up Um, it is possible to do that with lock free data structures, but it is not quite as straightforward because imagine that you You detect that the the if you have a lock free ring buffer You just sort of do a lock free read. You notice that it's empty. Now. What do you do in order to block? You need to block in such a way that someone else can wake you up when there's data there um You can see the axis Yes, so you're right that um You could totally have a push You could totally have a polling api Um, if you had a lock free ring buffer, but you could not have a blocking one Which is what you want uh, or It depends right like we might want to be able to provide both but they sort of necessitate different designs Even in a non-blocking design You don't actually want to be polling all the time. You want to get notifications when you should poll At least that's how sort of the current async infrastructure works And that means that you have to be able to somehow give wake ups um Okay, so here we're going to have to do some kind of wake up And we don't quite know how we're going to do that yet or We have some idea, but I haven't told you um All right, let's see how it complains about that. So main, uh In some sense main is just going to no longer exist But we kind of want main, right? Um, and so specifically what we're going to do with main is main is going to do trust Uh interface new Let i f is let I Like so and then we're going to do i.listen Or i.bind to port 9 000 We're also going to do Uh 9001, but we're not going to listen on any other ports um And then Then we're going to thread spawn move this Uh, this is going to do loop Are actually is going to do while let okay stream So this is going to look very similar to how we write networking code, which is sort of intentional because we've we've um Built it on top of the same kind of design Actually while I remember we're going to have to do Uh in bold drop for tcp listener Which I haven't talked about yet Basically just all listening on the port. Uh, we're also going to have to do the same thing for stream To basically send a fin And we're also going to have to do the same for interface. I think Because the interface has to Uh tear down the the network and stuff all right, so Uh while there's another while l1 dot accept Uh the print line god connection On 9 000 And then it's just going to drop it And this is going to be God connection on 9001 Okay, so we have some problems in sorts lib line 80 Uh That returns nothing That's a good point. Yeah, like what happens if this errors? Um if on packet errors, for example, I think we're going to have this return a An io result. Why is this not okay? Oh it needs uh needs to be a reference Uh why is 74 not okay because this has to be a quad Why is this not okay? Because Expected nothing found results. Oh, right Um this join handle is now for an i o result this because that's what the packet loop terminates with And line 79 doesn't need to be great And line 80 This is a an artifact of how The borrowing rules work I think we're going to have to do this for that to work So the the problem here, let me Recap that So it says cannot borrow cm is mutable more than once at a time Uh specifically it's saying we're borrowing cm dot connections here and we're borrowing cm dot pending here And that's not okay. It's saying And you might think well, this is weird normally it should realize that these two borrows are of different fields And so why does it care like borrowing those mutably separately should be fine The reason here is cm is a mutex guard And because of that because it's a mutex guard we're going to d rough and We're using the d rough mutate on cm to give us the underlying connection manager, right, but but That d rough borrows the entire mutex guard And so here it's really saying you can't borrow the entire mutex guard mutably again because you're already borrowing it here So what i'm doing here is basically a reborrow. So i'm i'm D reference so i'm doing the d rough mute here Which gives me a mutable reference directly to the connection manager as opposed to the the mutex guard And then it can distinguish that these are different fields Uh Main is complaining why Uh No method named accept found Oh, right because we call win and and try to try accept Let's just have it be called except because even though it's not quite right Uh mismatch It's saying five Uh Function body does not return J h one is this J h two is this One dot join j h two dot join And then I guess okay So this is a really stupid server, right all it does is just like starts up two services and then prints whenever it gets a connection Um, but it is a much nicer interface than the giant main function We had before this actually makes the interface look somewhat similar to what the the real rust network interface looks like Uh some unused things that's great Um, and we want to use also thread here Cannot borrow l one is mutable. That's fine Right, and we're not actually using the streams for anything for the time being And we want to make sure that these threads didn't fail Great. Okay. So now this compiles. Uh, it won't actually run correctly because we haven't implemented the blocking Um, but this is a good step forward Um, but let's go ahead and implement the the drops as well so when Um, when tcp listener gets dropped We basically want that to be the same as sort of unbinding right like now We're not going to accept any connections on the port that the tcp listener initially was listening on And the way that's going to work is pretty straightforward really it's going to be Almost exactly what you expect it to be we take the lock um Then we do cm dot pending dot remove self dot port right um So this is going to be uh pending And uh, we also sort of want to drop all of those connections so, um, remember that the code that we wrote for in tcp um, so The code we wrote for the packet loop, right? It gets a packet If it gets a packet for a quad that it didn't have and that port is currently open It's going to go ahead and accept that connection But imagine that this means that you might have it might have accepted a bunch of connections And then we go ahead and drop the tcp listener What do we do about all the connections that have been accepted or that we're about to remove from pending? Well, we sort of need to terminate them And the question becomes how So down here In the drop This is going to be I guess For quad in pending And here we're going to have to do something to uh Shut down or Uh terminate All terminate cm dot connections quad Which is something we haven't done yet So currently we're going to leave that as unimplemented and that unimplemented is only going to be hit if there are any pending Right, so this is basically the same as like assert pending is empty Um, except that we get to write out what we want this pattern to eventually be Uh, the drop for tcp stream is somewhat similar In that it is going to do It's going to take the lock it's going to remove itself expect And this is a similar kind of expect of Or actually this This is going to be if let Some see is that right because it could be that the other side has Disterminated the connection if the other side is terminated the connection and we just drop the tcp stream That's not a problem. It's only if we still have the connection open Then we need to do the same thing. We need to terminate this connection Uh, but we probably don't want to sort of forcibly terminate it. We just want to like send fin on this So this is similar to this bit down here This is similar to shutdown. So arguably this could be like self dot shutdown type or deal um Because it's basically doing the same thing. I guess we can do this here Just to leave the to do for ourselves um All right, and I guess maybe we don't need drop for interface Uh, well, so for this um When the last We sort of want some way to do a clean exit, right? So currently the um This this packet loop thread remember we we spawned a thread to do the packet loop That loop is just going to keep running forever. It doesn't It doesn't know when to exit um But if you drop Every single tcp stream actually if you drop the interface Right, if you drop this and you've dropped all the Tcp streams and tcp listeners then we sort of want this this thread to shut down um So I think what we really want to happen here is Uh Well, we certainly want to do self jh. This is where it gets awkward. Well, so we want to join unwrap unwrap Uh, we also sort of but and we sort of want to drop self dot ih Right The problem is this won't actually work But this is what we want to do. Uh, and then we just want the We also need to make the packet loop somehow recognize So maybe we do something like uh on the connection manager like terminated I'm just going to be a bull or terminate Bull so this is going to do self ih lock unwrap Terminate is true And then we sort of want to Want to wait for that to actually finish running The problem that there are a couple of problems with this one of them is that this might be blocked on this receive Right, but here we know that we're going to have a timeout regardless. So here to do Set a timeout for this receive for buffers for timers or for tcp timers or sell Connection manager Terminate So at least we know that once we fix this to do this receive will eventually return And at that point it's going to recognize that it's been told to terminate and it's just going to terminate What exactly it terminating means is uh Another potential problem. So down here we might have something like to do if self.terminate Well And arc Get strong refs or something ih Is equal to one So that would mean that there's the the loop is the only thing that still has a handle Then Tear down all connections and return So this we still don't really know how to do but this business is a little tricky because We only have a mutable reference to self So we can't drop any of the fields inside of it and self.jh It's a join handle join also consumes the join handle Um, this is a pattern that you run into in drops that are always a little bit annoying um And so one way to fix this is to have an have interface be an option It's kind of sad But it does work So it's basically this Uh, and so it's uh self dot ih dot take prop and take dot expect Interface dropped more than once Is there an actor crate you could recommend? Um, I haven't really used any of them as far as I'm aware actics is sort of the biggest one Although actics, um They seem to be pretty half sort of trigger happy on doing unsafe things Uh, and I think that's a problem But I haven't looked at it in enough detail to really say for sure Um Apart from that, I don't know of any other actor libraries in the rest ecosystem Uh, yeah, so we the what we're doing here is basically just Asserting that drop This option is always going to be some until you call drop then they're going to be none and you cannot call drop more than once um, this has the unfortunate side effect of You need to be this Everywhere where you want to use the ih So down here ih Asmute unwrapped lock 133 ih is going to be some Some ih And jh is going to be some jh Yeah, so there's still a bunch of to-dos here the biggest to-do though that I think we sort of need to fix is the blocking business um Because without that if if we try to run this right now, I mean I can try but I don't know that it will do anything useful Probably break in all sorts of horrible ways That's interesting What happens if I do 9002? Actually, let's kill that for a second and run Uh T shark So we're going to start this we're going to run t shark We're going to run first on 9 000 So nothing responded on 9 000 which seems like a problem Not sure why that is Yeah, definitely not sure why that is so something is weird um Even beyond this so the packet loop I guess here E print line I got packet for known quad This for unknown quad listening So accepting so let's see what this does So this says got packet for unknown quad and it's claiming that no one is listening on that port Which is kind of weird because main should be listening Right 9 000 and 9 000 one So let's see what actually is in pending cm.pending Pending is empty That seems problematic um Why is pending empty uh bound to 9 000 let's see that these calls actually return Oh, they might not no it should have bound So this suggests that our binding isn't working I guess down here we're gonna Bound and then print out cm.pending and then I guess Just to make sure this doesn't drop Drop listener Oh, I know what's happening the listener does get dropped. It's because um Okay, so here's why This thread the these two threads are immediately going to start trying to accept connections on On these tcp listeners except though remember we haven't implemented blocking So they're just immediately going to return an error with Woodblock and so it's gonna the thread is immediately going to exit at which point it drops the tcp listener So it's no longer listening on that port. So we do need to fix this blocking issue So let's go ahead and do that. Let's keep those in for now. Let's get rid of that and let's get rid of that Okay, so specifically let's fix fix Blocking for accept because that is basically the the delta we have to what we had working before right So before you could accept and then it would just end the connection Now we want to go do a little bit better than that Okay, so the question is For accept how do we get this block to work? Right like this is our our nemesis right now um And the way we're going to do that is we're going to use what's known as condition variables So a con var is basically um a way to block a thread while waiting for something to happen And so the idea is that you're going to take a lock You're going to check a condition and if the condition is not true You're going to wait on a condition variable That someone else is going to notify you when changes And then you're going to check if the thing is now true. Otherwise, you're going to wait again and so the basic pattern is uh Sort of like this you take a lock Right, you take a lock you check whether the condition is true And while it is not you wait and the weight implicitly releases the lock until the condition is true again So here we're going to do This is going to be loop or this is going to be Um, so this blocking is going to be So we're going to need a condition variable somewhere But here we're checking the condition and if the condition is not true We're going to take sort of cm dot pending var Dot wait and give it the cm and say cm is equal to that dot unwrap Right, so this pattern is basically uh We're going to keep releasing the lock And blocking until someone tells us we should wait up on this Um, now there are a couple of ways in which we could implement this We could have a single condition variable across all tcp listeners, but that's probably not what we want We probably want sort of a condition variable per Actually, it might not even matter Because the lock is global anyway But let's go ahead and make this a pending variable be per port So now what we're going to have is an in the connection manager instead of having pending just be this We're going to have a pending Which is going to have a vec dq quad And also a condition variable And the restrictions are Each con var can be used with precisely one mutex Um And I assume that you can use one mutex with multiple con bars They don't say anything to the contrary, so Yeah, okay great Uh, so this is also going to hold a con bar So it's going to be this is going to be uh Quads and this is going to be Var I guess And so the pending is going to hold a A bunch of pending this is implement default sort of hope so great Okay, so this is going to derive default Um, let's see whether that what fails to build right now Um, so this is going to be dot Uh quads Right and this is going to be Actually, let's do this this way Uh, I don't know if that's okay. I wonder whether the mutex has to be stored the con bar has to be stored outside of the mutex Yeah, it does doesn't it? Well, that's kind of awkward Can I clone the con bar? No, I can't can I So the the issue is the We need to when we wait on a con bar We need to give it to the mutex guard But if the con bar is inside what the mutex is guarding Then we can't give it the mutex guard because that would give up our handle on the on the bar Um interesting That tells me that we're going to do this in a Is this still going to be a slightly stupid way to do this but So We're going to have struct in man Uh, I don't know what to call this yet. I'm going to call it foobar for now and we're going to change that name as quickly as we can so it's going to have This the sort of manager, which is going to be the mutex Uh, and it's going to have a pending bar Which is going to be a con bar It's going to have a receive bar Although we're going to ignore the other bars for now, but eventually it's going to have those Uh, I don't know what the foobar is going to be called yet Um, the reason we want to do this is because it means now Uh This is going to be self dot h dot manager Dot lock And down here We're going to do cm is self dot h Dot pending bar dot wait cm Right, so this is now waiting On the the pending bar is now just in the In the thing that we have an arc to Um, it is not inside the mutex itself Which means that this now goes back to being a beck dq This is no longer quads So this now needs to be manager This needs to be manager This needs to be manager This needs to be manager This needs to be manager This needs to be manager. This needs to be manager. This needs to be manager Uh, and we need sync con bar The reason it's a little sad to have one con bar for all pending is that now you have to notify Basically all the tcp listeners whenever any connection is received. So we might want to try to do better than that So now of course now we have the Uh, now we have except we have a way for except to block Um, and the loop is going to make sure that even if we get woken up and it's not for our connection We're just going to realize that this is still not the case. And so we're going to block again um The problem of course is we have to have something that notifies us to wake this up when the The pending state changes right and that's going to be here in the packet loop Right the packet loop down here um Once it realizes that it has woken that it has added a pending thing um, it is going to have to do essentially um I mean, this is why we have this to do here, right? So this is going to be doing ih dot pending bar dot notify Um, so if you look at con bar it has Wait wait until which we don't actually need Not wait timeout not wait timeout not wait timeout Uh notify one I think we need so unfortunately currently we need notify all So this is going to call ih pending bar Uh notify all And we're gonna before we do that We are going to drop the Uh cm Well that's awkward we really want to drop the lock here So I think this is going to be drop cmg Because we want to drop the guard so that we release the lock So that when these things that are notified are woken up, uh, the lock will already have been released Oh, hey that compiled how about that? So let's see what happens now if I connect to 2000 illegal instruction Not yet implement. Okay. So this, uh, got packet for unknown quad, but it's listening on 9 000, which is what we wanted And then it got another packet for a known quad. There was a connection on 9 000 I forgot to run t shark Uh, and then it ran into not yet implemented online 222 Which is dropping the tcp stream Um, and dropping the tcp stream currently We're just gonna sort of ignore because we know that the our underlying tcp implementation Currently it will just send a fin immediately if you remember this from part one Um, we set it out such that the moment it gets a connection it sends fin It just goes i'm done i'm done And so we don't actually need to do this yet, but we do want this to eventually happen So let's try that again Uh, let's start this this time And let's try to connect Okay So that's interesting Uh Oh, this is like a an old leftover. Let's let's run that again Wait, why is it getting packets for 9 000? That's unfortunate. Oh, it's like the kernel still hanging behind Uh So that's kind of annoying Um, so let's have it bound to a different port Let's just say that it now listens on 6 000 And let's get rid of some of this other stuff. It's not terribly important for now All we want to see is that this interface works Okay, so currently it's not getting anything weird Start this is no weird traffic going on and we're going to connect on 6 000 So it got it still gets some old packets from 9 000 This is because we didn't shut down that connection correctly. So the our linux kernel is still trying to close that connection We're just going to ignore that But we're going to connect on 6 000, which is where we're now listening. So 6 000 connects Okay, so We got a packet for the unknown quad 6 000 We're listening on that port, right because we bound to it We got a packet then for that and now we say we got a connection. So this is the setup Think of this as um, this is the syn This is their act of our syn act Uh Then we got the connection and then this down here is we drop the tcp stream And so therefore the we get rid of the quad Which might not be what we want Uh So It sends in we send syn act It sent act Uh, we sent fin act. It sent act Great. So we have closed the outgoing connection. It hasn't closed the connection to us because I haven't told Uh netcat to stop sending stuff yet But it did drop the connection because we dropped the stream um Which is not really what we wanted because it means that if I now send this It will just get very confused about the state. Um, so maybe How do we want to do this even Basically when the tcp stream gets dropped, it's unclear. We really want to forget about it straight away Um Drop for tcp stream Um Specifically, I think we don't want to uh, so to do eventually Uh, remove from cm dot connections Right because um, even after the user Drops the tcp stream. There's still a bunch of handshaking that needs to go on right like the We when we drop the tcp stream all we're really doing is sending them a fin They we still have to wait for them to act that otherwise we might have to resend the fin And so we don't want to get rid of all the tcp state for that connection Until we really know that all the the peer knows that we shut down Um, and so therefore we don't actually want to Do anything when we drop the stream except for send the fin And currently we don't have a good way to do that. We do that automatically. So currently we do nothing and drop Uh, so let's go ahead and try that And make that 7 000 this time All right, so now we're going to connect on 7 000 Okay, so notice now we didn't get any packet for unknown connection after here because we didn't drop Um, and so now we see the same pattern Uh, netcat sent sin we responded with sin ac it responded with ac to our sin We immediately send a fin ac And they ac our fin, but they have not sent a fin yet. I can then terminate netcat without sending any data Uh, at which point it sends a fin ac and we ac and at that point the connection is done And so now notice the kernel isn't doing any retransmissions or anything It it's totally happy with everything just worked out Right, it it really does think that this connection is is shut down and we can check this with tn so There are there are no connections listed for the tunnel right whereas if you look at If I did this Then now there's a connection listed here Right because it hasn't been shut down yet It's setting close weight because the other side has closed, but we have not yet closed once I terminate this This now closes And the connection goes away. So that means the entire sort of pipeline now works And also notice that the this means that except blocking is now working right the except blocks when we try to do except Um, and then it gets woken up when there's a connection available So that is exactly what we wanted to happen Of course, we haven't implemented any of the the sort of read or write stuff We haven't implemented any of the read or write stuff Let me think a little here So we have implemented read and write we just haven't like This incoming buffer and the out the unact buffer are just currently entirely unused We are writing data into it and reading stuff out of it But but that's all Maybe we should fix this Because that should be possible to detect Uh specifically in some sense what I want here is I want the following code to work Stream dot read To like whatever it doesn't really matter And then a search And zero So basically this is Making sure that we can do a read that read will block until the other side is shut down the connection And we're just checking that we only ever get no data Um, so let's see if we can get that to work I think we will probably not do all like data read and write in the stream Um, this is more sort of infrastructure stuff for getting the stream in the right place Um is getting the project in the right place But being able to block at least on There being no data for you to read or the connection has now been terminated seems useful Um, so specifically that's this to do here and read right where I guess it's also this to do in block Uh from what language did you come to rust? So I have worked on a bunch of languages before rust. I think the biggest ones for me were Probably go But I've done a bunch of different stuff. I've done a bunch of c programming too and like c plus plus um Okay, so for read we basically need to fill in these two blocks right Um, so let's look at our actually before we do that. Let's do this Add nice net like interface Um Right, so now uh, this is the code. We sort of want to work. So let's look at how our tcp code actually deals with fins um so On packet is really the tricky part um So here is when we get a fin from them So then we know that any subsequent read Yeah, there's a weird synchronization thing where twitch people can see the youtube chat But the youtube chat cannot see the twitch chat. I don't know why sadly um So this is the point at which we receive a packet that has a fin and at that point we know that there's just like no more data um And I guess in theory Oh, let's pull up the state diagram actually from tcp the Where is it? Here we go Okay, so at some point we receive a fin So here we receive a fin And here we receive a fin and here we receive a fin So what that means Is if we are ever in the state's close weight closing or time weight Or of course Last act or closed Then we know that we're done um, so what we're gonna do is On connection We're gonna have a pub create a fin um Hmm, I think what we want here is a um Sort of like Is close is Receive closed And that's going to be if self dot state I guess if let uh state time weight Is equal to that To do this is also uh close weight or last act or closed Or closing Those states also imply that the other side uh Any state after received fin So also those which we we have just haven't implemented those states yet Um, and otherwise of course false um And so now This is going to be If e dot Is receive closed And that So if the receiver is closed and the and incoming is empty, then we know that there's just We're done. We can just return. Okay Uh Zero Right No more data to read And no need to block Uh because There won't be anymore Right like we received a fin and so we know there's no more data coming from the other side The tricky part is still of course this uh if incoming is empty Then we do need to block and we need to figure out how to do that We're going to do it in a similar way to what we did for except right So if you remember except we now have this sort of loop down here, um And so this is going to turn into a loop That is probably going to end up being weird Uh This is going to be a loop It's going to return this Uh And we're do we're going to do it the other way around we're going to say like so If there is if we're done then we return zero if there's data we return that data Otherwise we have to wait And of course this is not going to be a pending var. This is going to be a receive bar And so now you see why this is sort of problematic, right? You see why having one con var For like all of the connections is problematic because it means that if any data arrives We need to wake up anyone who's waiting Which can be pretty costly. So we might want to find a way to sort of Split this split it up so that we have one con var per connection But for now, let's not do that So we're going to have a pending var and a receive var And now so if we do cargo check that should compile Why did it not compile 11 mute stream that's fine Okay, so that now compiles but of course currently no one is ever notifying on the receive var And so if we wait on it, we're never going to be woken up That does get a little bit tricky though because now How do we know Whether to wake someone up? Well We know that it would have to be on packet So we sort of need on packet to return something telling us what to wake up So let's go to tcp and find our Uh fn on packet so currently that returns an ira result nothing I think what we're going to have to need to do here is this is going to have to be Now ready So we're going to have to do a pub create uh enum now ready And it's going to say whether Read or write is now ready Does that roughly make sense so uh when you call on packet or there could also be none So when you call when you When we pass in here a packet that we received It's going to report back whether the this particular connection Is now available for reading is now available for writing or neither Or maybe both Actually, let's instead of having it say ready. This should be It's almost like available So that's going to be available for read for write for read write and none and So we're going to match on that And if it is uh read Actually, let's Yeah, if it is read Then we are Actually, let's do this Availability is this um if let What do we call it available tcp? available read or tcp available read write Is equal to a then I'll explain that in a second So if read if it's now available for reading Then we are going to do what we did down here Which is we're going to well regardless we're going to drop this we don't need that anymore Then we're going to notify all in the receive bar If it's available for writing, then you could imagine that we're going to do something like send var Right currently we don't have such a var but you could imagine The reason I want to compare before after here is if it was available for read before this And it's available for read after this. There's no reason to notify anyone Right because it was already available for reading But that is a performance optimization. It's always safe to notify. So we're going to just notify for now Of course that's going to break a bunch of stuff specifically Um I think what we're going to want to do here is any time we're about to return Okay We need to actually do like self availability So on connection We're going to have a fn availability Self uh that's going to return available Uh And a is going to initially be available none if uh, I guess if Self dot is received closed or Uh self dot incoming Is not empty Then a is going to be available This is a bitmap. I want a bitmask crate What is the um bit mask bit Bit flag But but it's not bit flags that I want What maybe it is bit flags Yeah, except I don't need it to be that I need I want like flag Flags anyone remember the name of this crate the one that gives you Basically a way to or together enums. I feel like it's bit flags bitmask Bit maybe really is just bit flags. I mean, I don't particularly Want all right fine. Sure bit flags bit flags it is Um, so we're going to depend on bit flags is 1.0 And then down here, we're going to say that instead of having available be an enum. It's going to be one of these So struct available it's going to be a const read This can be a u8 Uh const write So I guess one two three four. No. Yes No binary Oh my god. Yep That way we don't need these and now This is going to be So what does this give me? Great that one So I want empty I want a or equals available read And we don't this is going to be like a to-do Set available right And then it's going to return a and now up in lib world We're going to say if a dot is Is it is set? I guess just or a tcp available Uh read and this So does this have a why doesn't this have an is set? Because that's really what I want. Oh contains That's what I want Contains this Uh, how do you create unique types in rust? What do you mean create unique types? All right, let's see whether that compiles probably not Uh, this needs to use bit flags Available is private this has to be pub create 285 So this has to be self dot Uh Availability this probably has to be the same And this has to be the same There are also some questions here about whether To do take into account self dot state Right Although this check might be enough for read. I'm not entirely sure It might only have to be taken into account for write 238 Oh No, this should not be public. All right, let's try that out for size Uh Well like closures, they are really unique because their type is created by the compiler except they're probably a clone copy I'm not sure what you're referring to so Oh you want uh Type that the compiler names for you. I don't think there's I mean arguably Uh enum variants that are structs are sort of like this But you can name them. They just have other names Um I don't think there's a good way to do that. No What what were you thinking that you would use such a type for it's almost like a Like an anonymous type Um, okay, so we're running and in theory we should run this and now Let's see what happens if we try to connect Oh, we forgot to change main to actually print. Oh, no, it should have printed read data. It did not So something is broken. Um It sent a fin act, but we did not think that we read any data so Why is that the case? Um now available for reading Okay So that would be the hope that that prints out Then it should notify. So I guess the other thing is read um Checking on status for read I guess trying read No more data Status is this this Receive closed and see incoming is empty Not yet Block So let's see it tries the read it gets false true So this got packet for known quad. Why does it not print anything useful? Specifically it doesn't say this So that means on packet is not returning read set Yeah, I don't think there are anonymous types in rust yet. I could be wrong, but I don't think so Um, so this means that a does not contain available for read. What does it contain? availability Uh a and I'm sure it's gonna complain about the port. So let's do a different port if I do this Availability empty, right? That's what we expect now this It still says availability empty Even after I terminated the other side. So the question is why? um So now we need to look into here and look at availability The question becomes Why is this not returning true? Let's see computing availability So It should be available for read either if the channel is closed So or either either if the other side has sent a fin in which case we should be in the time wait case or If Incoming is not empty. Although we're currently not writing anything to incoming. So this should always be True. No, this should always be false This so without the exclamation mark should always be true, but this should always be false um So this means that where this is not returning true Let's make sure that this has debug asked if closed when in Self.state. Okay, that's fine and now in that Okay, so I asked if closed when in fin weight one so in fin weight one fin weight one is the state where we have sent a fin And it has not yet been act Uh, this is the act for our fin. So now we're in fin weight two, which is we have sent a fin and it has been act Um, but they have not yet sent a fin And here the reader is blocked because well There's nothing for them to read um specifically Closed as false and empty is true at which point there's nothing to read. So it's blocking Here is what we get the This is where we get the fin So why is it not transitioning? That's really what's wrong So we're in fin weight two and we just received the fin So why oh why? Are we in fin weight two that seems wrong? because um That last packet is the fin act And that fin act should move us from fin weight two into time weight Because we received the fin So why is that not the case? Well Guess it's time to figure that out Not okay, could be one case No act could be another case Otherwise Date okay, so hopefully we get down here But it doesn't really seem like it So I guess Let's go back to seven Okay, so the pattern is pretty much what we expect so the packets are always the same God packet bad sequence That's probably wrong I don't believe you that that has a bad sequence number So they're it's saying that this is a bad sequence number Which seems incorrect and then it's saying that the second one does not The second one is not okay All right, so let's figure out what's going on here specifically It's saying that the first one is not okay I'm almost sure that the is between wrapped that has been updated is wrong But so okay is being set to false. So where is it being set to false? Good old print debugging two Three and then the one Oh, no, the first one said bad sec, which is here So this should be self-send una act and self-send next Wrapping add one okay, so all the initial packets seem to be fine Um And it's saying this is a bad sec two less than two less than three. What's the requirement for that again? uh, this is for Uh, the requirement is that That This is less than this Is less than or equal To that So this is a bad act two two three But how I see I don't think that's right Okay, so we're failing this check for this incoming packet um And this is saying that the act number in the packet Which is like the sequence number the other site expects next Has to be greater than The last thing we haven't heard act something's not right. Uh, so this is in When you receive a packet, right? Um, and processing So we specifically want segment arrives If you remember from the previous stream at the bottom of the Of the tcp spec, there's a basically a state machine that you can follow And what we're looking for here is if we're in established fin weight or one or fin weight two Um, so that's this All right, so this is the acceptability check and the acceptability check we already is already fine Yeah, so this this is not the check we're failing We're failing The ac field ac bit is on And we are in established fin weight one or fin weight two if uh This is really just saying that Only if it's between here do we update which things haven't been, uh, acknowledged But we shouldn't stop processing just because of that Right accept data So if what we get is an unacceptable act that doesn't mean that we shouldn't handle the packet It just means that the act shouldn't be used to update our Tracker of how much data the other end is received So for example, we might still want to process the fact that they sent us a fin, right? And crucially we don't want to return Great, let's try that Yeah So we get the packet we notice that it's a fin We compute the availability we notice the availability is read because it's now been fin So we're now setting ourselves available for reading that wakes up the thread that blocked when trying to do a read And it recognizes that it can read it reads the data it gets the zero Excellent Okay, let's get rid of some of these Don't need these anymore Actually, let's keep these because those are probably going to turn in handy We might want to add like a logging library that we integrate with this Because otherwise debugging this is going to just keep being a pain a connection A connection closed No more data Great, so this is still sort of stupid, right? This is still we're trying to read zero data But at least it means that we can correctly detect when there's no more data coming We have now of like E prints and stuff Oh, right. Uh, we probably don't want that E print that protocol Probably want that probably don't want that That's fine Right, so this is still broken but broken just in the sense that we never write anything to incoming right so remember from TCP we have this Assert that data is empty. So if they're if the other side tries to send a data as data, we're just going to crash anyway Um, and so that's fine but The infrastructure we have here shouldn't theory now be enough that we should be able to do reads Um, and reads are probably easier than writes because reads don't require any uh retransmissions Sort of you need to retransmit x but we'll get back to that later. Um Yeah Okay, so let's uh Go ahead and commit that Let's me push that out I think we might actually want to end it there. I don't want to dive into doing All the timer and data stuff This stream because I feel like this is sort of a nice Termination point for cleaning up the entire external API Um So I think we're going to end it there And that way we can leave sort of the the data aspect of this and sort of that includes things like congestion window sizes Timeouts that sort of stuff To cover that entirely In the next stream I think that's what we're gonna do. Um, I've pushed the the code that I have so far So feel free to sort of play around with it. Um I think Yeah, so we'll leave it there and the next stream as I mentioned at the beginning will not be until Uh Early may sometime. I don't know exactly when but sometime early may Um So I guess stay tuned for that. Um, we still haven't figured out a name for the stream Um I'll stick around and chat a little bit after I end the video, but uh, if you have thoughts for First of all, whether it should whether there should be a name whether there should be sort of a dedicated twitter account or something like that Um, and if so, what do you think it should be called then feel free to ping me I I genuinely want like potential names for this. Um But in the meantime, take care. Enjoy the start of spring and I will see you in, uh, may Bye