# RC - Overview do código ## Receiver ### Topo do ficheiro - Macros definidos incialmente, que já vêm dos ficheiros que forneceram - Macros de degub BYTE_TO_BINARY(para ser mais fácil dar display-não fazem nada) - STOP é uma variável global que é usada para os loops de espera de resposta ### Main - Main começa com validação de argumentos da linha de comandos e os passos gerais de tratar a serial port (que já é fornecido pelo código base) - chama as funções que devem ser feitas no trabalho llopen, llread, llclose e depois volta a colocar a porta de série nos valores default e fecha a conexão ### llOpen - Chama ```setProtocol```, que basicamente faz um loop infinito até conseguir ler o SET que vem do lado do emissor. - O ```readSet``` faz um loop de leitura. Basicamente todos os autómatos de verificação que eles têm no trabalho funcionam de uma maneira semelhante - Começa com o stop e flag a falso, e lê byte a byte e depois começa uma cadeia de ifs seguindo a lógica do pacote (neste caso o SET), a cada if que acerta vai lendo mais um byte até confirmar o pacote inteiro (quando chega a uma nova flag), e para o loop. Caso falhe a meio dá erro, salta os outros ifs e verifica se o que está atualmente no buffer é uma flag (imaginando que o envio foi mal feito/perdeu conexão e já voltou a enviar outro SET), assim não precisa de fazer o read inicial do loop e pode ir logo para a cadeia de ifs. - Fico na dúvida se não precisava de um timeout aqui, mas acho que não é necessário. ### llRead - Como tem muitas variáveis globais, às vezes fica um pouco confuso perceber o que as funções que o read usa, estão a fazer internamente. - Pelo que eu entendo, pega na variável global e aloca 2 * o tamanho de uma frame (deve ser para garantir que não há overflows). Primeiro aloca com o default de 256 e depois aloca com o tamanho máximo de pacote que vier do outro lado do emissor, com o START (não percebo porque o faz duas vezes, mas pronto). Aloca memória também para o buffer global, o fileData cujo tamanho também recebe do START packet. - ```recieveStart```(muito confuso para mim xd), corre o dataProtocol que está descrito aqui em baixo, devolvendo na global var data o pacote de start. Depois vai campo a campo (file size, file name, max frame size), fazer o inverso da criação deste pacote, ou seja alocar arrays com o tamanho de cada campo, ler os vários bytes que compõem esse campo e depois fazer um cáculo de shifts para obterem o valor real de cada um. Ficam com um cálculo deste género para cada cena ```fileSize = (aux0 << 24) | (aux1 << 16) | (aux2 << 8) | (aux3);```. Aqueles ^0xFF em parte servem para remover o & 0xFF que é obrigatório fazer segundo os slides e o outro acho que é para quando o valor é negativo, conseguires extrair a informação corretamente, quando o teu número está em complemento de 2, se te lembras é aquela representação em que o número fica assim 111111101010, ou seja se guardares um 1010 do lado do emissor, como ele tem de preencher 8 espaços, ele presumo que ele é negativo e faz 11111010, mas na verdade querias 00001010 (acho que é isso que estão a colmatar). É esquisito terem sempre 4 termos (vai sempre até v[3]) penso eu, porque pode variar em função do nome que lhe dás etc, mas pode ter ficado assim por convenção ou facilidade. No final o start é guardado globalmente e o tamanho, nome e maxframesize também são passados para variáveis globais. - ```dataProtocol```, aparentenemente é o que usam para ler pacotes de __dados__ do outro lado no geral. Loop infinito que usa o readData(). Se o readData retornar -1 basicamente o outro lado já mandou o ficheiro todo inclusivé o DISC, se for 1 o BCC do pacote (a paridade dos bytes) deu bosta e tem de lhe mandar o REJ para repetir o envio. Quando corre bem, estás em duas situações (mas correu bem logo manda-se o RR). Ou estamos a receber um Start e não um pacote genérico de dados, daí a var global ``ìsStart``estar a 1, e fechamos o loop ou é um pacote de dados (que é verificado se é duplicado no readData, com a var gloabl duplicate). Se passar o teste, é guardado no ficheiro. - ```saveFileData``` (guarda dados que recebeu no buffer global fileData), verifica quantos bytes já leu e compara com o tamanho do ficheiro a receber (valor que veio do START). Se já leu todos ou até a mais, ignora esses dados a mais e espera pelo END do lado do emissor para acabar com o loop de leitura (com o STOP global) e passar para o llclose. Senão faz uma escrita no filedata co offset do número de bytes que já recebeu. A matemática que está inicialmente serve para perceber se a escrita que vais fazer é do teu tamanho de pacote predefinido (256 ou o que vem no START), ou os dados restantes que já não ocupam um tamanho total de um pacote. Como a divisão no início da função é inteira, significa que para um ficheiro de 11B com frames de 5B, tens 11/5= 2, e depois tens 11 - 5 * 2 = 1B (é o resto dado pelo operador %, não sei porque não fizeram assim), tens 2 * o tamanho de um frame e um resto que vai ter de ser escrito à parte, com tamanho especificado. Os contadores de bytes globais são também incrementados. - ```receiveEnd```, a única coisa que faz é ver se é um pacote de END (com o primeiro byte) e comparar com o start inicial que tem de coincidir (pode dar asneira porque ele fica à espera dele se não me engano, mas o sender acaba por mandar outro penso eu) - ```readData``` é mais uma vez um daqueles automatos com ifs (só que como são mais condições ainda fica mais confuso). Loop inicial em que lê a 1ª flag, depois vai campo a campo (no caso do controlo verifica se o pacote que está a receber é o que espera com ```dataFrameNum```, questão dos duplicados). A uma jigajoga interessante no loop que começa em //DATA (linha 315), basicamente sabes que o BCC2 vai ser o último byte, ou seja lês tudo para um buffer, dizes que a posição i de tmpData é igual ao último valor lido pelo bcc2 e o novo bcc2 é igual ao novo valor lido. Isso faz com que, ao ler a flag e terminando a leitura interna dos dados (este loop ```while(receiving == 0)```), o bcc fique com o último byte lido antes da flag. Ao longo dos ifs tens os elses para quando cada passo corre mal, alguns geram retorno como o duplicado que retorna 0 e outros retornam erro como um bcc inválido. Da linha 334-376 tem o destuffing do pacote (reverter o processo que incluí os acaracteres de escape na própria mensagem, de uma forma convencionada para saberes o que está lá-foi e continua a ser confuso para mim perceber como se faz) - ```writeToFile```, pega no filename que recebeu no receivestart e copia o conteúdo que foi guardado no buffer fileData para o ficheiro na pasta de receção em ./received/"nomedoficheiro" - ```sendREJ/sendRR```, constroem os pacotes de controlo REJ e RR, de acordo com as especificações dos slides (atenção à questão de especificar o 1 ou 0 do STOP and WAIT, de acordo com o último pacote que recebeu/não recebeu) e escreve para a porta ### llClose - Chama ```disconnectProtocol``` - A seguir à transmissão dos dados propriamente dita, entras nesta função, esperas em loop inifinito pelo readDisc (com um áutomato semelhante aos outros, mas mais simples). Usa ```sendDiscWithAlarm``` para construir o pacote de DISC e envia para o outro lado se possível. - Antes disso chama ```signal(SIGALRM, sendDiscWithAlarm)```, o que isto faz é dizer ao programa/OS que quando um sinal de alarme for acionado, execute de novo a função de envio do DISC. Nalarm é incrementado a cada disc enviado no ```sendDiscWithAlarm```e se for maior que 3 ele simplesmente desiste de enviar mais coisas e retorna com exit(1), nesta fase em princípio já tens o ficheiro e quase não importa. Se correr bem e enviar o DISC tenta receber um Ack do lado do emissor com o ```readUA(fd)``` em loop também. O tempo até um novo alarme é definido todas as vezes no final de cada tentativa do disconnect com ``` alarm(3);``` - Não percebo é porque é que é a única que tem os alarms de timeout ao longo do receiver (no ```sendDiscWithAlarm```), acho que as outras também precisavam disso. ## Sender ### Topo - Mesmas coisas que receiver, algumas varáveis para os argumentos da linha de comandos (percentagem de erros simulados, maxframesize etc) alarmes, buffers de escrita e frame atual. ### Main - Lê e valida args da linha de coamndos e coloca a porta de seŕie com aqueles defaults dados no código base, dando flush do buffer dela para começar limpa. - aloca globalData para mais tarde ler do ficheiro, chama as funções principais, llopen, llwrite e llclose ### llOpen - Chama ```setConnection```. Muito semelhante ao lado do receiver só que tem o tal timeout com o alarm que o receiver tem no disconnect das 3 tentativas e dos 3 segundos . Envia o SET para iniciar o protocolo (```sendSetWithAlarm```) e espera que o receiver responda com o UA. Cria o SET com os valores dados pelo slide e valida o UA com um automato semelhante aos outros. ### llWrite - Começa com ``` sendStartOrEnd(filename,1);```, que envia o start ou end baseado no segundo argumento. Constroem os campos individuais de cada componente do pacote dse Start/End, type, size e value do nome do ficheiro, tamanho e maxFrameSize. Usam ``` stat(nameDest,&st)``` para saber o tamanho em bytes. Não sei se aqui estar tudo hardcoded é uma boa política, porque acho que é mais dinâmico, os campos não precisam de ter obrigatoriamente o tamanho que eles colocaram aqui, devia ser dinâmico para ocupar o mínimo espaço possível. Copiam isso para o ```globalData```, que depois é enviado com o ```int transferData()```. - ```transferData()``` tem o mesmo mecanismo de alarme que as outras funções têm para o envio, com ```sendDataWithAlarm``` a ser acionada no alarm que é estabelicido por ```sendDataWithAlarm```. Depois entra num loop (enquanto estamos dentro das três tentativas) para esperar pela resposta de RR/REJ do receptor. Se for RR, paramos o loop e termina a função. Se for REJ, colocamos o resend a 1, e chamamos recursivamente a função (não é uma boa prática honestamente, especialmente se meter sinais ao barulho) - ```sendDataWithAlarm```, aloca um buffer com 2* o tamanho máximo de um frame. Se ainda estivermos dentro das três tentativas (não esquecer que é chamada dentro de sendDataWithAlarm, caso contrário sai do programa), cria o header do pacote, e percorre globalData (a variável global de leitura) passando-a para o buffer que vai ser enviado. Durante este passo faz o tal stuffing dos caracteres de escape se for necessário. ```c if (globalData[i] == 0b01111110 || globalData[i] == 0b01111101) { buf[n] = ESC; buf[++n] = globalData[i] ^ STUFF; n++; } ``` - A mesma coisa é feita ao incluir o BCC, no caso de ser igual a um destes caracteres. - Se existir uma probabilidade de erro da frame dada na linha de comandos é colocada com isto ```buf[size-2] = buf[size-2] ^ 0x0F;``` - De seguida chama-se write para transferir a informação e aumenta-se o nAlarm para dizer que uma tentativa foi realizada e o alarme é atualizado. - ```readDataResponse``` - ```sendFileData```, tem uma lógica semelhante à leitura dos dados no lado do receptor. Armazena-se todo o ficheiro dentro de um buffer global e faz-se o cálculo de quantos frames temos de enviar (incluindo aquela questão de não ser necessário um buffer completo para o que restar do ficheiro). - Não sei muito bem o que l2 e l1 fazem, ainda por cima tem lá um TODO, mas suponho que tenha a ver com uma convenção do tamanho de frame algures. ```c currentDataSize = frameMaxSize; int j = 0; //offset atual do buffer que tem o ficheiro for(int i = 0; i < numFramesToSend; i++){ memset(globalData, 0, frameMaxSize*2); // resetar para 0 o globalData, que corresponde a uma frame memcpy(globalData, buffer + j, currentDataSize); // copia os dados do ficheiro transferData(); // envia-se a globalData j += currentDataSize; //aumenta o currentDataSize numOfFrame++; // passa para a frame seguinte ``` - Depois utiliza o mesmo cálculo para perceber quanto falta para terminar o ficheiro em bytes, e realiza esse último envio, tudo feito com ```tranferdata``` ### llClose - Semelhante ao lado do receiver. Sigalarm para ```sendDisconnectWithAlarm```, até receber um DISC do lado do recetor indicando a disconexão total, durante três tentativas, senão exit(1). Depois envia um UA indicando que recebeu o DISC. ### Resultados gerais (do meu trabalho) - Regra geral quanto maior a probabilidade de erro, pior a eficiÊncia de transferência (erros nos dados são piores que no header porque requerem uma leitura completa do pacote que acaba por ir para o lixo) - tempo de propagação prejudica a eficiência - baudrates acima de 76800 prejudicam a eficiẽncia (provavelmente a porta de série não suporta) - Tamanho do ficheiro aumenta proporcionalmente com a eficiência, ficheiros mais pequenos têm diferenças mais notórias de tempo de transferência, ficheiros maiores vão praticamente iguais e a diferença torna-se mínima ## Possíveis alterações - O que alterava mais o trabalho para não haver confusão é dividir o receiver e o sender para ficheiros mais pequenos (separando nas camadas da app, linklayer application layer etc). Ter um makefile também vai parecer mais pro - Se vires funções como o disconnect e readUA são muito iguais dos dois lados, ou seja se passassem para um só ficheiro (utils p.e.) em princípio poderiam ser partilhadas. - As variáveis globais poderiam passas a ser uma struct passada por apontador, mas acho que não faz muita diferença - Mudar os nomes das funções e das variáveis mais gerais. - Tirar os pragma region, pelo que percebi não fazem nada a nível utilitário Se ainda tiver algum tempo, vejo se consigo ajudar nesta parte. O importante é que continue a compilar, no máximo se uma nova versão não der quando chegarem à sala mudem para a anterior do repositório