Je vais donc commencer par vous décrire les objets que j'ai créé pour cette implémentation, je poursuivrai par leur mise en relation et je terminerai par leur intégration dans wmiirc.php.

Le modèle objet

Pour savoir ce que je devais faire pour créer ce client, J'ai commencé par lire la documentation relative au protocole, qui, même si elle n'est pas très parlante et ne regorge pas d'exemple, m'a permis de définir le modèle objet à mettre en oeuvre.

En résumé, 9Plan est un protocole client/serveur qui permet de manipuler un système de fichiers virtuel.

Lorsqu'un client désire une information, par exemple le contenu d'un fichier, il effectue une requête vers le serveur à l'aide d'un Tmessage, et reçoit la réponse de ce dernier sous la forme d'un Rmessage.

Ces deux types de messages ont un en-tête identique, composé de trois champs :

  1. La taille en octet du message.
  2. Le type du message.
  3. Une étiquette, qui permet de faire correspondre un Rmessage à un Tmessage et qui permet de gérer des transactions.

En PHP, cela peut se traduire par la classe abstraite suivante :

abstract class wmiiMessage
{
protected $type = 0;
protected $tag = 0;

public function __construct($type)
{
$this->type = $type;
}

public function getType()
{
return $this->type;
}

public function setTag($tag)
{
$this->tag = $tag;
return $this;
}
}

Vous noterez que j'ai omis de gérer la taille du message car je n'en ai aucunement besoin dans le cadre de wmiirc.php.

Nous avons donc maintenant une classe abstraite capable de prendre en charge la partie commune entre les Tmessage et les Rmessage.

Nous allons maintenant voir la définition des ces deux types de message à partir de cette classe de base.

Le Tmessage sert aux demandes d'informations. Il doit donc être encodé avant d'être envoyé au serveur.

le Rmessage sert à la réception des informations, en réponse à un Tmessage. Il doit donc être décodé lors de sa réception.

Il est donc possible d'implémenter les Tmessage et les Rmessage de la manière suivante, en dérivant notre classe abstraitre de départ :

abstract class wmii9PlanTMessage extends wmii9PlanMessage
{
public function __construct($type, $tag)
{
parent::__construct($type);
$this->tag = $tag;
}

public function encode()
{
$data = pack('Cv', $this->type, $this->tag) . $this->encodeMessage();

return pack('V', strlen($data) + 4) . $data;
}
}

abstract class wmii9PlanRMessage extends wmii9PlanMessage
{
public function __construct($type, $data)
{
parent::__construct($type);

$this->decode($data);
}

protected function decode($data)
{
$this->tag = current(unpack('vtag', $data));

$this->decodeMessage(substr($data, 2));

return $this;
}

protected abstract function decodeMessage($data);
}

Nous disposons maintenant de l'ensemble des classes nécèssaires pour gérer les différents types de message requis par le protocole.

Il suffit de créer une classe par type, héritant soit de wmii9PlanTMessage ou de wmii9PlanRMessage suivant qu'il s'agit d'un Tmessage ou d'un Rmessage, par exemple de la manière suivante :

class wmii9PlanTwalkMessage extends wmii9PlanTMessage
{
const type = 110;

protected $fid = 0;
protected $newFid = 0;
protected $path = '';


public function __construct($tag, $fid, $newFid, array $path)
{
parent::__construct(self::type, $tag);

$this->fid = $fid;
$this->newFid = $newFid;
$this->path = $path;
}

protected function encodeMessage()
{
$data = pack('VVv', $this->fid, $this->newFid, sizeof($this->path));

foreach ($this->path as $wname)
{
$data .= self::encodeString($wname);
}

return $data;
}
}

L'implémentation des différents types de message se fait donc très rapidement, via la surcharge des méthodes decodeMessage() et encodeMessage(), sans aucune duplication de code, le code commun aux différentes classes étant encapsulé dans les classes parentes.

L'encodage et le décodage

Les lecteurs attentifs auront remarqué l'utilisation des fonctions pack() et unpack() dans le code ci-dessus.

Leur mise en oeuvre est rendu nécéssaire par le fait que le protocole 9Plan est un protocole binaire.

Il n'y a rien à dire de bien particulier sur ces fonctions, si ce n'est qu'il y a de nombreux bugs s'y rapportant et que leur mise en oeuvre s'apparente à celle des fonctions de la famille printf().

Cependant, 9Plan fait appel à des mots binaires de 64 bits que ne savent pas gérer les fonctions pack() et unpack().

Pour contourner le problème au niveau de l'encodage, j'ai concaténé deux mots binaires de 32 bits, en donnant la valeur 0 au mot binaire de poids fort.

Pour le décodage, je ne décode que les 32 premiers bits du mot binaire de 64 bits.

Cette solution n'est pas parfaite et pour être honnête, elle me déplaît foncièrement.

En effet, mon code ne sera pas capable de gérer correctement les valeurs dépassant la capacité d'un entier non signé sur 32 bits, qui est le plus grand mot binaire que sait gérer PHP sur une architecture 32 bits.

Cependant, dans le cas de wmiirc.php, des valeurs dépassant la capacité de ce mot binaire ne peuvent pas survenir, et je fais contre mauvaise fortune bon coeur.

Cela me fait d'ailleurs penser que les implémentations de pack() et unpack() sont dépendantes de l'arhitecture matérielle utilisée.

Il y a donc de fortes chances pour que ceux qui utilisent mon code sur une architecture 64 bits rencontrent quelques difficultés.