[jitsi-dev] PseudoTCP


#1

Hi !

As I mentioned in another post my task for GSoC 2012 will be to
implement PseudoTCP for ICE4J library.
It's already implemented in Google's Libjingle library. Main
protocol's logic is located in class:
http://code.google.com/p/libjingle/source/browse/trunk/talk/p2p/base/pseudotcp.cc?spec=svn127&r=127

I've examined it's code and I was able to run some tests of
communication in this protocol. Because target Java implementation
must be fully compatible with google's implementation I'm going to use
existing code during implementaion and testing(I have already some
code in c++ based on Libjingle's unit tests).

In my GSoC proposal I've posted class diagram, but I'm posting it here
again so that everyone can see it.
http://www.gliffy.com/pubdoc/3468570/L.png
It describes how I plan to implement PseudoTCP in Java. As a starting
point I'll "copy paste" pseudoTcp logic class to java and I'm going to
make each method working the same way as in Libjingle's
implementation. Maybe some kind of unittests. Later I'll adopt it to
java reality and make some necessary optimizations.

After it's implemented it must be integrated with ICE4J library. At
first I was thinking about adding new transport type, but now I think
that is not the right way. Probably this should be resolved at some
higher application level. Maybe provide some wrapper class for
DatagramSocket. I mean that when application wants to use pseudoTcp it
asks agent about candidates using different harvesters for UDP
transport type. When ice discovery is finished it should wrap
datagramSocket into pseudoTCP socket and make use of reliable data
transfer over UDP.

Regards eventual new transport type in ICE4j I'm not sure how
connectivity checks are performed in ICE discovery. Is UDP
connectivity enough to make pseudoTCP connection ? Or maybe there have
to be established both ways UDP connectivity ? Or maybe it should test
if on another "UDP side" there is pseudoTCP implementation running
(handshake should be performed)?

As to the schedule I plan to make it before the official schedule and
deadlines - starting tomorrow :slight_smile: (in case there won't be any nasty
problems as usual)

Looking forward for some comments.

Best regards,
Pawel


#2

Hello,

Under the link: http://code.google.com/p/pseudo-tcp-java-impl/ I've
created svn repository to host working copy of my pseudo tcp java
implementation. I've put there a whole NetBeans project. Please let me
know if it's available for public read access ?

Regards current progress. I've succesfully transferred current
implementation to java. However I'm not sure if I understood all parts
correctly and properly converted ambiguous parts. At the moment it
only compiles without errors of what I am sure. Also I've created some
util calsses for buffers along with some simple unit tests.

While converting the code I've encountered a problem with using
unsigned 32 ints in java. I'm able to send and read them correctly.
But there is a problem when I would try to use them in buffers
handling. There are 2 fields in pseudo tcp header regards that:
- sequence number
- acknowledgment number
In c++ implementation these are used with buffers to mark offsets of
data. However in Java, arrays use int for index so I'm unable to use
long.
I made that assumption basing on that part of code:

(PseudoTcpBase.java:806):
// Adjust the incoming segment to fit our receive buffer
if (seg.seq < m_rcv_nxt) {
   long nAdjust = m_rcv_nxt - seg.seq;
   if (nAdjust < seg.len) {
      seg.seq += nAdjust;
      seg.data = ScrollBuffer(seg.data, nAdjust);
      seg.len -= nAdjust;
   } else {
      seg.len = 0;
   }
}
long available_space = m_rbuf.GetWriteRemaining();
if ((seg.seq + seg.len - m_rcv_nxt) > available_space) {
   long nAdjust = seg.seq + seg.len - m_rcv_nxt - available_space;
   if (nAdjust < seg.len) {
     seg.len -= nAdjust;
   } else {
     seg.len = 0;
   }
}

So as I understand pseudo tcp is using sequence number to handle also
byte stream not only to mark packets order. At the moment I don't know
how is handled the situation when we are transferring data longer that
uint32. Maybe it gets reseted at some point, then I should just do so
when it gets too large. I'll be working to figure this out, but any
sugesstions are welcome.

Cheers,
Pawel


#3

Hey Powel,

I'd call for practicality here and face the fact that having a Java
byte array of length 2^32 - 1 (or 2^31 - 1 for that matter) in the
network stack of a desktop application is plain impossible because
that amounts to 4 (or 2 in the case of Integer.MAX_VALUE) gigabytes.
I'd say that you can safely cast a long Java byte array index to int.

Best regards,
Lyubomir

···

2012/5/12 Paweł Domas <paweldomas@gmail.com>:

In c++ implementation these are used with buffers to mark offsets of
data. However in Java, arrays use int for index so I'm unable to use
long.


#4

Hi,

just a small hint: when converting an "unsigned int" (32bit) to an Java
long then mask with 0xffffffff. You may also have a look into the source
...neomedia.RawPacket.java . There you'll find a lot conversion functions
to and from network byte buffer / Java types.

Regards,
Werner

···

Am 13.05.2012 09:37, schrieb Lyubomir Marinov:

2012/5/12 Paweł Domas <paweldomas@gmail.com>:

In c++ implementation these are used with buffers to mark offsets of
data. However in Java, arrays use int for index so I'm unable to use
long.

Hey Powel,

I'd call for practicality here and face the fact that having a Java
byte array of length 2^32 - 1 (or 2^31 - 1 for that matter) in the
network stack of a desktop application is plain impossible because
that amounts to 4 (or 2 in the case of Integer.MAX_VALUE) gigabytes.
I'd say that you can safely cast a long Java byte array index to int.

Best regards,
Lyubomir


#5

Hi,

Thanks for help ! I'll check this out.

···

2012/5/13 Werner Dittmann <Werner.Dittmann@t-online.de>

Am 13.05.2012 09:37, schrieb Lyubomir Marinov:
> 2012/5/12 Paweł Domas <paweldomas@gmail.com>:
>> In c++ implementation these are used with buffers to mark offsets of
>> data. However in Java, arrays use int for index so I'm unable to use
>> long.
>
> Hey Powel,
>
> I'd call for practicality here and face the fact that having a Java
> byte array of length 2^32 - 1 (or 2^31 - 1 for that matter) in the
> network stack of a desktop application is plain impossible because
> that amounts to 4 (or 2 in the case of Integer.MAX_VALUE) gigabytes.
> I'd say that you can safely cast a long Java byte array index to int.
>
> Best regards,
> Lyubomir
>
Hi,

just a small hint: when converting an "unsigned int" (32bit) to an Java
long then mask with 0xffffffff. You may also have a look into the source
...neomedia.RawPacket.java . There you'll find a lot conversion functions
to and from network byte buffer / Java types.

Regards,
Werner

Regards,
Pawel


#6

Hi,

I got some news about hows it going :slight_smile:

First of all I was able to exchange correct packets with libjingle's
implementation. Also some small amounts of data, but sill have some small
problems probably with my FifoBuffer or test code written in C++.

Also I've prepared base class that I'll be using to write further unit
tests. I plan to rewrite them from libjingle. They test main protocol logic
simply by writing bytes from array to array introducing delays and
eventually specified packet loss. When all tests will be completed I can be
quite sure that there's no errors in rewritten protocol.

First unit test which transfers some data in one direction is already
completed and it almost works. Almost because it takes too much time to
transfer the data as there are some breaks in data exchange. It's probably
something with delayed acks and I'm tracking it down.

What I am going to do this week is to make current code compatible with
used convention. Then next are remaining unit tests. I'm not sure how much
time I'll be able to spend for it as there's coming test at school by next
Tuesday.

Regards,
Pawel


#7

Hey Pawel,

Hi,

I got some news about hows it going :slight_smile:

First of all I was able to exchange correct packets with libjingle's
implementation.

This is really great! Congrats!

Also some small amounts of data, but sill have some
small problems probably with my FifoBuffer or test code written in C++.

Also I've prepared base class that I'll be using to write further unit
tests. I plan to rewrite them from libjingle.

I like that. We will also plug them into the ice4j.org build loop.

They test main protocol
logic simply by writing bytes from array to array introducing delays and
eventually specified packet loss. When all tests will be completed I can
be quite sure that there's no errors in rewritten protocol.

I am curious about one thing here. Do the tests involve actually passing
bytes over the network, or do they have a mockup layer to replace the
sockets?

First unit test which transfers some data in one direction is already
completed and it almost works. Almost because it takes too much time to
transfer the data as there are some breaks in data exchange. It's
probably something with delayed acks and I'm tracking it down.

What I am going to do this week is to make current code compatible with
used convention.

In case someone's wondering, I am the one who insisted ;).

Then next are remaining unit tests. I'm not sure how
much time I'll be able to spend for it as there's coming test at school
by next Tuesday.

We are very much on track so I don't think this would be a problem at
all. Good luck with the exam!

Emil

···

On 21.05.12 20:17, Paweł Domas wrote:


#8

Hi,

> They test main protocol
> logic simply by writing bytes from array to array introducing delays and
> eventually specified packet loss. When all tests will be completed I can
> be quite sure that there's no errors in rewritten protocol.

I am curious about one thing here. Do the tests involve actually passing
bytes over the network, or do they have a mockup layer to replace the
sockets?

Yes it's some kind of mockup layer. But it doesn't replace sockets as
PseudoTcpBase class doesn't work on sockets yet. For this task there is
PseudoTcpStream class which will expose final public interface.

So the base class exposes methods to get notified about new packet data and
it requires to get in constructor an IPseudoTcpNotify instance. This
interface will be notifed when new user data has been read from packet and
as well on some certain protocol state changes. This interface must also
provide method which will send or pass created packet data to another side.
In this case this interface is implemented by test class which simply
passes that data to another-side's istance(there are local and remote
instances in test class). When this "data passing" is performed it can
decide not to pass it, so we have a lost packet simulated. It's not my
idea, but it's how it works in libjingle and I've liked it.

Regards,
Pawel

···

2012/5/21 Emil Ivov <emcho@jitsi.org>


#9

Hi all,

I have posted report on my gsoc
blog<http://pawel-gsoc2012-jitsi-xmpp.blogspot.com/2012/06/first-progress-report.html>
about
current project status.

Now I work on final Java interface for the protocol. In general it works
with DatagramSocket class to handle UDP and exposes implementation of
InputStream for reading data and OutputStream for writing. Please let me
know if is this ok as I didn't receive any feedback about that.

I was thinking about some interface rather than direct usage of
DatagramSocket which will make this more portable. Maybe there is some kind
of similiar mechanism in ice4j already ? I'm at the moment when I have to
know how itegrate it with ice4j. Could you please give some directions ?

Implementation of InputStream is quite simple, but I have few questions
about OutputStream. As I've understood the
documentation<http://docs.oracle.com/javase/1.4.2/docs/api/java/io/OutputStream.html#write(byte[])>,
when
we call write operations it is ok to simply buffer the data, so I'll be
passing it to pseudoTcp send buffer. In case when the buffer is full should
I block write operations ?

The next question is about flushing OutputStream. Should I wait until the
data is sent ? Or do I have to wait untill it will get acknowledged ?

Regards,
Pawel


#10

Implementation of InputStream is quite simple, but I have few questions
about OutputStream. As I've understood the documentation, when we call write
operations it is ok to simply buffer the data, so I'll be passing it to
pseudoTcp send buffer.

That's how I understand the Javadoc of the OutputStream and the send
method in the C++ code.

In case when the buffer is full should I block write
operations ?

OutputStream#write is a void method so I think the method returns once
it has written all bytes i.e. subsequent writes will block until the
current one returns. Moreover, the C++ code seems to be returning
EWOULDBLOCK i.e. blocking sounds like the expected behavior in the
Java version given that it returns no flags.

The next question is about flushing OutputStream. Should I wait until the
data is sent ? Or do I have to wait untill it will get acknowledged ?

Have you tried to figure out how it works with java.net.Socket?
Looking at its source code on the Java and the respective native sides
may be of use in the case. I don't know of a specific use case of ours
which requires a certain flush behavior. Anyway, isn't it easy to
change the behavior if it turns out later on that there are different
expectations?

Since we're talking about such fine details in the write
implementation, do you provide explicit support for SO_LINGER?

···

2012/6/11 Paweł Domas <paweldomas@gmail.com>:


#11

> The next question is about flushing OutputStream. Should I wait until the
> data is sent ? Or do I have to wait untill it will get acknowledged ?

Have you tried to figure out how it works with java.net.Socket?
Looking at its source code on the Java and the respective native sides
may be of use in the case. I don't know of a specific use case of ours
which requires a certain flush behavior. Anyway, isn't it easy to
change the behavior if it turns out later on that there are different
expectations?

Thanks for reply. I'll work on it.

Since we're talking about such fine details in the write
implementation, do you provide explicit support for SO_LINGER?

There's an option for a gracefull close, but the problem is that there's no
explicit close action from the protocol at the moment (there's no closing
procedure). As I understand the code, when we close not gracefully protocol
stops all transfers and discards any data which will arrive
eventually. Probably it was used in libjingle that way that there was known
the size of data and connection was being closed when specified amount had
been transfered. It would be quite easy though to send reset packet on
close action. When reset packet is receivied there is returned a connection
reset error and the connection is closed. But maybe it may cause some
errors in apps which use libjingle although the transferd was completed
succesfully.

Regards,
Pawel

···

2012/6/12 Lyubomir Marinov <lubo@jitsi.org>


#12

What about integration with ice4j ?
Is this stream interface sufficent or should I go deeper into the Ice
protocol ?

Regards,
Pawel