#!/usr/bin/perl

=head1 NAME

CSSJ::CTIP - CTIPプロトコルハンドラ

=head2 概要

CSSJの転送プロトコル(CTIP)の低レベルの部分を扱います。

通常、プログラマがこのパッケージを直接使う必要はありません。

=head2 作者

$Date: 2006/03/30 10:58:01 $ MIYABE Tatsuhiko

=cut
package CSSJ::CTIP;
use CSSJ::Helpers;

require Exporter;
@ISA	= qw(Exporter);

use strict;
use Symbol qw(qualify_to_ref);

=head1 定数

=head2 REQ_PROPERTY

リクエストパケットタイプ:プロパティ

=cut
sub REQ_PROPERTY { return 1; }

=head2 REQ_RESOURCE

リクエストパケットタイプ:リソース

=cut
sub REQ_RESOURCE { return 2; }

=head2 REQ_MAIN

リクエストパケットタイプ:本体

=cut
sub REQ_MAIN { return 3; }

=head2 REQ_DATA

リクエストパケットタイプ:データ

=cut
sub REQ_DATA { return 4; }

=head2 REQ_END

リクエストパケットタイプ:終了

=cut
sub REQ_END { return 5; }

=head2 RES_ADD

レスポンスパケットタイプ:フラグメント追加

=cut
sub RES_ADD { return 1; }

=head2 RES_INSERT

レスポンスパケットタイプ:フラグメント挿入

=cut
sub RES_INSERT { return 2; }

=head2 RES_ERROR

レスポンスパケットタイプ:エラーメッセージ

=cut
sub RES_ERROR { return 3; }

=head2 RES_DATA

レスポンスパケットタイプ:データ

=cut
sub RES_DATA { return 4; }

=head2 RES_END

レスポンスパケットタイプ:終了

=cut
sub RES_END { return 5; }

=head1 connect

C<connect IOHANDLE ENCODING>

セッションを開始します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item ENCODING 通信に用いるエンコーディング

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub connect (*$) {
  my $fp = shift;
  my $encoding = shift;
  my $str = "CTIP/1.0 $encoding\n";
  $fp = Symbol::qualify_to_ref($fp, caller());
  
  defined(CSSJ::Helpers::write($fp, $str)) or return undef;
  return 1;
}

=head1 req_property

C<req_property IOHANDLE NAME VALUE>

プロパティを送ります。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item NAME 名前

=item VALUE 値

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub req_property (*$$) {
  my ($fp, $name, $value) = @_;
  $fp = Symbol::qualify_to_ref($fp, caller());

  my $payload = length($name) + length($value) + 5;
  defined(CSSJ::Helpers::write_int($fp, $payload)) or return undef;
  defined(CSSJ::Helpers::write_byte($fp, CSSJ::CTIP::REQ_PROPERTY)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $name)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $value)) or return undef;
  return 1;
}

=head1 req_resource

C<req_resource IOHANDLE URI [MIME_TYPE ENCODING]>

リソースの開始を通知します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item URI URI

=item MIME_TYPE MIME型

=item ENCODING エンコーディング

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub req_resource (*$;$$) {
  my $fp = shift;
  my $uri = shift;
  my $mimeType = shift || 'text/css';
  my $encoding = shift || '';
  $fp = Symbol::qualify_to_ref($fp, caller());

  my $payload = length($uri) + length($mimeType) + length($encoding) + 5;
  defined(CSSJ::Helpers::write_int($fp, $payload)) or return undef;
  defined(CSSJ::Helpers::write_byte($fp, CSSJ::CTIP::REQ_RESOURCE)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $uri)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $mimeType)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $encoding)) or return undef;
  return 1;
}

=head1 req_main

C<req_main IOHANDLE URI [MIME_TYPE ENCODING]>

本体の開始を通知します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item URI URI

=item MIME_TYPE MIME型

=item ENCODING エンコーディング

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub req_main (*$;$$) {
  my $fp = shift;
  my $uri = shift;
  my $mimeType = shift || 'text/html';
  my $encoding = shift || '';
  $fp = Symbol::qualify_to_ref($fp, caller());

  my $payload = length($uri) + length($mimeType) + length($encoding) + 5;
  defined(CSSJ::Helpers::write_int($fp, $payload)) or return undef;
  defined(CSSJ::Helpers::write_byte($fp, CSSJ::CTIP::REQ_MAIN)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $uri)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $mimeType)) or return undef;
  defined(CSSJ::Helpers::write_bytes($fp, $encoding)) or return undef;
  return 1;
}

=head1 req_write

C<req_write IOHANDLE DATA [LENGTH]>

データを送ります。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=item DATA データ

=item LENGTH データの長さ

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub req_write (*$;$) {
  my ($fp, $b) = @_;
  $fp = Symbol::qualify_to_ref($fp, caller());
  my $len = length($b);
  $len = $_[2] ? ($_[2] >= $len ? $len : $_[2]) : $len;

  my $payload = $len + 1;
  defined(CSSJ::Helpers::write_int($fp, $payload)) or return undef;
  defined(CSSJ::Helpers::write_byte($fp, CSSJ::CTIP::REQ_DATA)) or return undef;
  defined(CSSJ::Helpers::write($fp, $b, $len)) or return undef;
  return 1;
}

=head1 req_end

C<req_end IOHANDLE>

終了を通知します。

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=back

=head2 戻り値

B<成功なら1,失敗ならundef>

=cut
sub req_end (*) {
  my ($fp) = @_;
  $fp = Symbol::qualify_to_ref($fp, caller());

  defined(CSSJ::Helpers::write_int($fp, 0)) or return undef;
  return 1;
}

=head1 res_next

C<res_next IOHANDLE>

次のレスポンスを取得します。

レスポンス(array)には次のデータが含まれます。

- 'type' レスポンスタイプ
- 'anchorId' 挿入する場所の直後のフラグメントID
- 'level' エラーレベル
- 'error' エラーメッセージ
- 'id' 断片ID
- 'progress' 処理済バイト数
- 'bytes' データのバイト列

=head2 引数

=over

=item IOHANDLE 入出力ストリーム(通常はソケット)

=back

=head2 戻り値

B<レスポンス,失敗なら空のハッシュ>

=cut
sub res_next (*) {
  my $fp = shift;
  $fp = Symbol::qualify_to_ref($fp, caller());
  
  my $payload = CSSJ::Helpers::read_int($fp);
  defined($payload) or return ();
  if ($payload == 0) {
    return (
      'type' => CSSJ::CTIP::RES_END,
    );
  }
  
  my $type = CSSJ::Helpers::read_byte($fp);
  defined($type) or return ();
  
  if ($type == CSSJ::CTIP::RES_ADD) {
      return (
        'type' => $type,
      );
      
  } elsif ($type == CSSJ::CTIP::RES_INSERT) {
      my $anchorId = CSSJ::Helpers::read_int($fp);
      defined($anchorId) or return ();
      return (
        'type' => $type,
        'anchorId' => $anchorId
      );
      
  } elsif ($type == CSSJ::CTIP::RES_ERROR) {
      my $level = CSSJ::Helpers::read_byte($fp);
      defined($level) or return ();
      my $error = CSSJ::Helpers::read_bytes($fp);
      defined($error) or return ();
      return (
        'type' => $type,
        'level' => $level,
        'error' => $error
      );
      
  } elsif ($type == CSSJ::CTIP::RES_DATA) {
      my $id = CSSJ::Helpers::read_int($fp);
      defined($id) or return ();
      my $progress = CSSJ::Helpers::read_int($fp);
      defined($progress) or return ();
      my $length = $payload - 9;
      my $bytes = CSSJ::Helpers::read($fp, $length);
      defined($bytes) or return ();
      return (
        'type' => $type,
        'id' => $id,
        'progress' => $progress,
        'bytes' => $bytes,
        'length' => $length
      );
  
  } else {
      warn ("Bad response type:$type");
      return ();
  }
}
