2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

HTTP/3 and QPACK event definitions for qlog の紹介

Posted at

本記事ではIETFで標準化が行われている、QUICなどのロギングを行うフォーマットであるqlogの、HTTP/3とQPACKに関するイベントを定義している「HTTP/3 and QPACK event definitions for qlog」について紹介します。

HTTP/3 and QPACK event definitions for qlog とは

qlogには発生したイベントをロギングする仕組みがあります。「HTTP/3 and QPACK event definitions for qlog」は、qlogにHTTP/3とQPACKのイベントを記録するための情報を定義しています。

具体手的には、イベントの種類を特定するためのname (category + event)とそのイベントをロギングするときに含むデータが定義されています。また、それぞれのイベントにはImportance という情報も定義されています。

なお、qlogの提案自体は2019年ころから行われていましたが、「HTTP/3 and QPACK event definitions for qlog」まだWG Draftになって間もない状態です。また、HTTP/3はDraft-34、QPACKはDraft-21を参照しています。

本記事は、Draft-00を参照しています。そのため今後の議論によって内容が大きく変わる可能性があります。

ドラフトのベースとなっているドキュメントがあるレポジトリや、議論されているIssueの状況は下記から参照可能です。

レポジトリ
https://github.com/quicwg/qlog

議論
https://github.com/quicwg/qlog/issues

qlog main schemaのおさらい

「HTTP/3 and QPACK event definitions for qlog 」を読む上で必要になるqlogの基礎知識は別記事にまとめてあります。

qlog main schemaのおさらい

nameについて

イベントのImportanceについて

QUICとHTTP/3・QPACKのログを同時に出力したい場合

qlogには、HTTP/3・QPACKのログとQUICのログを同時に出力することもできます。逆に、HTTP/3・QPACKのみ出力することやQUICのみ出力することもできます。

QUICと一緒に使う場合は、推奨されるファイル名やサーバーやクライアントのトレースの分割方法は、QUICのイベント定義のドキュメントを(Destination Connection IDの使用を推奨しています)優先します。

QUICと一緒に使わない場合、HTTP/3の接続にグローバルで一意なIDを設定することが推奨されます。そのIDは、qlogのgroup_idや、ファイル名、ファイル識別子、vantagepoint typeのサフィックスに使います。例えば、abcd1234_server.qlogは、接続のGUIDがabcd1234のサーバーサイドのトレースを含みます。

qlog main schemaとのつながり

このドキュメントでは、main schemaで定義されたフィールド(例えば、name, category, type, data, group_id, protocol_type, 時間に関連するフィールド、importance, RawInfoなど)が再利用されます。

このドキュメントでHTTP/3やQPACKのために定義されているイベントをトレースファイルに含める場合、protocol_typeの配列には"HTTP/3"を含める必要があります。

Raw packet and frame information

qlogのメインスキーマで定義されている、RawInfoは再利用されます。

RawInfo は生のバイト長やバイトを保存しておくことで、パケタイゼーションの振る舞いの調査やチューニング、エンコードやフレーミングのオーバーヘッドの確認などに使うために定義されています。

HTTP/3の場合、ヘッダ長 header_length

header_length = RawIngo:length - RawInfo:payload_length

で計算されるため定義はされていません。

class RawInfo {
    length?:uint64; // the full byte length of the entity (e.g., packet or frame) including headers and trailers
    payload_length?:uint64; // the byte length of the entity's payload, without headers or trailers

    data?:bytes; // the contents of the full entity, including headers and trailers
}

いくつかの場合で、lengthフィールドは、明示的にフレームのヘッダに反映されます。例えば、すべてのHTTP/3フレームは、明示的のペイロード長をヘッダの中に持ちます。

参考: DATAフレームの定義

   DATA Frame {
     Type (i) = 0x0,
     Length (i),
     Data (..),
   }

HTTP/3 and QPACK event definitions

現時点では、HTTP/3とQPACKそれぞれにhttpqpack というカテゴリが割り当てられています。

HTTP/3に対するイベントは、http:parameters_set のような形式で定義されます。

QPACKに対するイベントは、qpack:state_updated のような形式で定義されています。

http

HTTP/3 には以下のようなイベントが定義されています。

event type Importance サマリー
parameters_set Base SETTINGSフレームで設定されるHTTP/3やQPACKの設定が含まれます
parameters_restored Base 0-RTT接続するときに、再利用するパラメータを記録します
stream_type_set Base ストリームの種類を記録します。
frame_created Core HTTP/3のフレームが生成されたことを記録します
frame_parsed Core HTTP/3のフレームを解析したことを記録します
push_resolved Extra プッシュされたリソースが上位から要求されたことを記録します

parameters_set

Importance: Base

parameters_set はHTTP/3とQPACKの設定を含みます。

多くの場合これらの設定はHTTP/3のSETTINGSフレームによって受信されます。

HTTP/3の設定は、SETTINGS_MAX_FIELD_SECTION_SIZE があります。SETTINGS_MAX_FIELD_SECTION_SIZEは1つのHTTPメッセージに含まれるヘッダのサイズを制限します。

QPACKの設定は、5. Configuration に書かれているSETTINGS_QPACK_MAX_TABLE_CAPACITYとSETTINGS_QPACK_BLOCKED_STREAMSが含まれます。

SETTINGS_QPACK_MAX_TABLE_CAPACITY は、デコーダー側が決めるQPACKで使用する動的テーブルのサイズの最大値になります。

SETTINGS_QPACK_BLOCKED_STREAMSはブロックされるストリームの上限を設定します。QUICはストリームの到着順序を保証していないため、ダイナミックテーブルのエントリーが参照しても即座に処理できない場合があります。そしてそのような場合ストリームはブロックされます。この値によってそのストリームの数の上限が決まります。

これらのパラメータは一度設定されたら変更されない場合が多いです。しかし、典型的には接続の異なるタイミングで設定されます。なので、このイベントは異なるフィールドを持った複数のインスタンスが記録される可能性があります。

設定には、ローカルで設定されるものとリモートのピアから設定されるものがあります。どちらが設定したかはownerフィールドに反映されます。

owner フィールドは必ず含まれなければいけない設定ではないですが、もし含めるのであれば、単一のイベントインスタンスに含まれるすべての設定に対してownerが正しい必要があります。したがって、もしクライアントとサーバーの二つの側からの設定を明示的にログに残したい場合は、二つの別々のイベントインスタンスを発行する必要があります。

このイベントは仕様書には記されないフィールドを持つことが可能です。これは、例えば、unknown (greased)な設定や、パラメータの拡張を含むことが可能です。

Data:
{
    owner?:"local" | "remote",

    max_header_list_size?:uint64, // from SETTINGS_MAX_HEADER_LIST_SIZE
    max_table_capacity?:uint64, // from SETTINGS_QPACK_MAX_TABLE_CAPACITY
    blocked_streams_count?:uint64, // from SETTINGS_QPACK_BLOCKED_STREAMS

    // qlog-defined
    waits_for_settings?:boolean // indicates whether this implementation waits for a SETTINGS frame before processing requests
}

サーバープッシュの設定について

サーバープッシュは、HTTP/3の設定やパラメータによって使用可能になるわけではありません。

その代わりに、MAX_PUSH_IDフレームのやり取りによって決まります。

クライアントが、送信したMAX_PUSH_IDよりサイズの大きいPushIDを持ったストリームは使えません。

参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34#section-7.2.7

A client MUST treat receipt of a push stream as a connection error of type H3_ID_ERROR (Section 8) when no MAX_PUSH_ID frame has been sent or when the stream references a Push ID that is greater than the maximum Push ID.

なので、サーバープッシュの設定はframe_createdframe_parsed イベントによるMAX_PUSH_IDフレームの生成・解析によって記録されます。

parameters_restored

Importance: Base

QUICの0-RTTを使うときに、HTTP/3のクライアントは以前の接続のサーバーの設定を保持して再利用することが可能です。

参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-http-34#section-7.2.4.2

When a 0-RTT QUIC connection is being used, the initial value of each server setting is the value used in the previous session. Clients SHOULD store the settings the server provided in the HTTP/3 connection where resumption information was provided, but MAY opt not to store settings in certain cases (e.g., if the session ticket is received before the SETTINGS frame). A client MUST comply with stored settings -- or default values, if no values are stored -- when attempting 0-RTT. Once a server has provided new settings, clients MUST comply with those values.

このイベントは、HTTP/3の設定を復元して0-RTT接続で利用されることを示します。

   Data:

   {
       max_header_list_size?:uint64,
       max_table_capacity?:uint64,
       blocked_streams_count?:uint64
   }

このイベントはparameters_setと同様に、このドキュメントで定義されていないフィールドやカスタムの設定を含むことができます。

stream_type_set

Importance: Base

このイベントはストリームタイプが既知になったときに記録されます。典型的には、ストリームが開かれて、ストリームタイプを表す情報が送信または受信されたときにログを発行します。

Bidirectional Streamは、data ストリームになります。

Unidirectional Streamはいくつかの種類があります。

HTTP/3 で規定されているものは以下の二つです。

また、"0x1f * N + 0x21"のIDを使うReserved Stream Typesというのもあります。

QPACKでは、以下の二つが定義されています。

  • Encoder Stream
  • Decoder Stream

参考: https://datatracker.ietf.org/doc/html/draft-ietf-quic-qpack-21#section-4.2

ストリームIDはQUICのレベルで分割されているため、多くの情報はストリームIDを見ることで推測できるようです。
そうだとしても、 この情報が明確に示されているとデバッグに大いに役立つため、このイベントには"Base"を割り当てられています。

   {
       stream_id:uint64,

       owner?:"local"|"remote"

       old?:StreamType,
       new:StreamType,

       associated_push_id?:uint64 // only when new == "push"
   }

   enum StreamType {
       data, // bidirectional request-response streams
       control,
       push,
       reserved,
       qpack_encode,
       qpack_decode
   }

frame_created

Importance: Core

このイベントは、HTTP/3のフレームを作成したときに発行されます。そのタイミングはHTTP/3のデータがQUICのレイヤに渡されたのと同じタイミングである必要はありません。

QUICレイヤに渡されたタイミングに関するイベントはdata_moved イベントで定義されています。

   Data:

   {
       stream_id:uint64,
       length?:uint64, // payload byte length of the frame
       frame:HTTP3Frame, // see appendix for the definitions,

       raw?:RawInfo
   }

HTTP/3では、ヘッダのオーバーヘッドを減らすためDATAフレームは任意の長さを持つことが可能です。そのような場合、DATAフレームは、多くのQUICパケットにまたがりストリーミングされる形になります。

この場合は、frame_createdイベントはフレームヘッダのために1回だけ発行されます。そして、それ以降にストリームされるデータは、data_movedイベントでログに記録されます。

frame_parsed

Importance: Core

このイベントはHTTP/3のフレームがパースされたタイミングで発行されます。

これは、QUICレイヤでHTTP/3のデータが実際に受信されたタイミングと同じである必要はありません。

QUICレイヤからデータを受け取るタイミングに関するイベントはdata_moved イベントで定義されています。

   Data:

   {
       stream_id:uint64,
       length?:uint64, // payload byte length of the frame
       frame:HTTP3Frame, // see appendix for the definitions,

       raw?:RawInfo
   }

HTTP/3では、DATAフレームはフレームヘッダのオーバーヘッドを減らすために任意の長さを設定できます。frame_createdと同様に、DATAフレームは複数のQUICパケットにまたがります。そして、ストリーミングされる形になります。

この場合、frame_parsedイベントはフレームヘッダに対して1回だけ発行され、ストリームされたデータは data_moved で記録をします。

push_resolved

Importance: Extra

このイベントは、プッシュされたリソースがウェブブラウザなどのHTTP/3の上位のアプリケーションから要求される(使用される)場合か、捨てられる場合に発行されます。

このイベントは、予期されないPushの振る舞いをデバッグするのに役立ちます。

{
    push_id?:uint64,
    stream_id?:uint64, // in case this is logged from a place that does not have access to the push_id

    decision:"claimed"|"abandoned"
}

qpack

QPACKに関するイベントは、主に低レベルのQPACKの問題のデバッグに役に立ちます。

高レベルでは、プレインテキストのヘッダの値はHTTPのframe_createdframe_parsedに記録されるべきです。

qpack のカテゴリには以下のようなイベントがあります。

event type Importance サマリー
state_updated Base QPACKの内部変数が変わったことを記録します
stream_state_updated Core ストリームがブロック・アンブロックされたことを記録します
dynamic_table_updated Extra ダイナミックテーブルへのエントリーの挿入・削除を記録します
headers_encoded Base ヘッダブロックがエンコードされたことを記録します
headers_decoded Base ヘッダブロックがデコードされたことを記録します
instruction_created Base QPACKのinstructionが生成されてEncoder/Decoderストリームに追加されたことを記録します
instruction_parsed Base QPACKのinstructionがEncoder/Decoderストリームから読み込まれたことを記録します

qpackに対する parameter_set は定義されていません。
QPACKの仕組みをHTTP/3の拡張としてとらえています(HTTP/3の規格に載っていないという意味ととらえるとよさそうです)。そのため、自身ではparamsets_set のイベントは持たず、httpのparameters_setに統合されています。

他のHTTP/3の拡張は、SETTINGSフレームにhttp.parameters_set に記録されるかもしれませんし、独自のイベントとして記録されるかもしれません。

state_updated

Importance: Base

このイベントは、一つまたは複数のQPACK内部の変数が変わったときに発行されます。

現状で記述されているのは、ダイナミックテーブルのキャパシティ、サイズ、Known Received Count、Insert Count(ダイナミックテーブルに挿入されたエントリーの合計数)などがあります。

設定には、ローカルで設定されるものとリモートのピアから設定されるものがあります。どちらが設定したかはownerフィールドに反映されます。

owner フィールドは必ず含まれなければいけない設定ではないですが、もし含めるのであれば、単一のイベントインスタンスに含まれるすべてのセッティングに対してownerが正しい必要があります。したがって、もしクライアントとサーバーの二つの側からの設定を明示的にログに残したい場合は二つの別々のイベントインスタンスを発行する必要があります。

   Data:
{
    owner:"local" | "remote",

    dynamic_table_capacity?:uint64,
    dynamic_table_size?:uint64, // effective current size, sum of all the entries

    known_received_count?:uint64,
    current_insert_count?:uint64
}

stream_state_updated

Importance: Core

このイベントは、ヘッダーデコーディングリクエストまたはQPACK instructionによって、ストリームがブロック・アンブロックされた場合に発行されます。

このイベントは、HTTP/3の性能を観測する場合に影響が大きいためImportanceはCoreにされています。

Data:

{
    stream_id:uint64,

    state:"blocked"|"unblocked" // streams are assumed to start "unblocked" until they become "blocked"
}

dynamic_table_updated

Importance: Extra

このイベントは、QPACKのダイナミックテーブルの一つまたは複数のエントリーが挿入あるいは削除されたときに発行されます。

 Data:

{
    owner:"local" | "remote", // local = the encoder's dynamic table. remote = the decoder's dynamic table

    update_type:"inserted"|"evicted",

    entries:Array<DynamicTableEntry>
}

class DynamicTableEntry {
    index:uint64;
    name?:string | bytes;
    value?:string | bytes;
}

headers_encoded

Importance: Base

このイベントは、圧縮されていないヘッダブロックがエンコードされたときに発行されます。

このイベントのheadersフィールドに含まれるHeadersFrameはhttp.frame_craetedと重複します。実装者は、両方のイベントを発行するときにheadersフィールドをこのイベントから排除するかもしれません。

Data:
   {
       stream_id?:uint64,

       headers?:Array<HTTPHeader>,

       block_prefix:QPackHeaderBlockPrefix,
       header_block:Array<QPackHeaderBlockRepresentation>,

       length?:uint32,
       raw?:bytes
   }

headers_decoded

Importance: Base

このイベントは、圧縮されたヘッダブロックがデコードされたときに発行されます。

このイベントのheadersフィールドに含まれるHeadersFrameはframe_parsedと重複します。実装者は、両方のイベントを発行するときにheadersフィールドをこのイベントから排除するかもしれません

Data:

   {
       stream_id?:uint64,

       headers?:Array<HTTPHeader>,

       block_prefix:QPackHeaderBlockPrefix,
       header_block:Array<QPackHeaderBlockRepresentation>,

       length?:uint32,
       raw?:bytes
   }

instruction_created

Importance: Base

このイベントは、QPACKのinstructionが生成されてencoder/decoderストリームに追加されたときに発行します

Instructionには、4.3. Encoder Instructions4.4. Decoder Instructions があります。

Data:
   {
       instruction:QPackInstruction // see appendix for the definitions,

       length?:uint32,
       raw?:bytes
   }

instruction_parsed

Importance: Base

このイベントは、QPACKのinstruction (decoderとencoderの両方)が、encoder/decoderストリームから読まれたときに発行されます。

Data:

   {
       instruction:QPackInstruction // see appendix for the definitions,

       length?:uint32,
       raw?:bytes
   }

HTTP/3 data field definitions

ここでは、HTTP/3のフレームと、エラーコードの記録方法を定義しています。

HTTP/3 Frames

HTTP/3で定義されているフレームに対応する構造がログ用に定義されます。

type HTTP3Frame = DataFrame | HeadersFrame | PriorityFrame | CancelPushFrame | SettingsFrame | PushPromiseFrame | GoAwayFrame | MaxPushIDFrame | DuplicatePushFrame | ReservedFrame | UnknownFrame;

DataFrame

DATA フレ―ムの定義です

   class DataFrame{
       frame_type:string = "data";

       raw?:bytes;
   }

HeadersFrame

HEADERS フレームの定義です。
HeadersFrameには、QPACKの圧縮が適用されていない状態のplaintext のHTTP ヘッダを格納します。

headers: [{"name":":path","value":"/"},{"name":":method","value":"GET"},{"name":":authority","value":"127.0.0.1:4433"},{"name":":scheme","value":"https"}]

   class HeadersFrame{
       frame_type:string = "header";
       headers:Array<HTTPHeader>;
   }

   class HTTPHeader {
       name:string;
       value:string;
   }

CancelPushFrame

CANCEL_PUSH フレームの定義です。

   class CancelPushFrame{
       frame_type:string = "cancel_push";
       push_id:uint64;
   }

SettingsFrame

SETTINGS フレームの定義です。

   class SettingsFrame{
       frame_type:string = "settings";
       settings:Array<Setting>;
   }

   class Setting{
       name:string;
       value:string;
   }

PushPromiseFrame

PUSH_PROMISE フレームの定義です。

   class PushPromiseFrame{
       frame_type:string = "push_promise";
       push_id:uint64;

       headers:Array<HTTPHeader>;
   }

GoAwayFrame

GOAWAY フレームの定義です。

   class GoAwayFrame{
       frame_type:string = "goaway";
       stream_id:uint64;
   }

MaxPushIDFrame

MAX_PUSH_ID の定義です。

   class MaxPushIDFrame{
       frame_type:string = "max_push_id";
       push_id:uint64;
   }

DuplicatePushFrame

DUPLICATE_PUSH フレームはドラフト26以降で削除されたようです。

   class DuplicatePushFrame{
       frame_type:string = "duplicate_push";
       push_id:uint64;
   }

ReservedFrame

Reserved Frame Types の定義です。

   class ReservedFrame{
       frame_type:string = "reserved";
   }

UnknownFrame

QUICのUnknownFrameと値や使い方が被るので定義を再利用します。

   class UnknownFrame{
       frame_type:string = "unknown";
       raw_frame_type:uint64;

       raw_length?:uint32;
       raw?:bytes;
   }

ApplicationError

8.1. HTTP/3 Error Codes で定義されているエラーコードに対応しています。

   enum ApplicationError{
       http_no_error,
       http_general_protocol_error,
       http_internal_error,
       http_stream_creation_error,
       http_closed_critical_stream,
       http_frame_unexpected,
       http_frame_error,
       http_excessive_load,
       http_id_error,
       http_settings_error,
       http_missing_settings,
       http_request_rejected,
       http_request_cancelled,
       http_request_incomplete,
       http_early_response,
       http_connect_error,
       http_version_fallback
   }

QPACK DATA type definitions

ここでは、QPACKのinstructionsとField Line Representations の記録方法が定義されています。

QPACK Instructions

QPACKのEncoder InstructionDecoder Instruction に対応しています。

instructionは、名前や機能からEncoderのinstructionか、Decoderのinstructionかが一意に定まるので、Encoder/Decoder instructionを一つのtypeに含めているようです。

type QPackInstruction = SetDynamicTableCapacityInstruction | InsertWithNameReferenceInstruction | InsertWithoutNameReferenceInstruction | DuplicateInstruction | HeaderAcknowledgementInstruction | StreamCancellationInstruction | InsertCountIncrementInstruction;

SetDynamicTableCapacityInstruction

Set Dynamic Table Capacity の定義です。

   class SetDynamicTableCapacityInstruction {
       instruction_type:string = "set_dynamic_table_capacity";

       capacity:uint32;
   }

InsertWithNameReferenceInstruction

Insert With Name Reference の定義です。

  class InsertWithNameReferenceInstruction {
       instruction_type:string = "insert_with_name_reference";

       table_type:"static"|"dynamic";

       name_index:uint32;

       huffman_encoded_value:boolean;

       value_length?:uint32;
       value?:string;
   }

InsertWithoutNameReferenceInstruction

Insert With Literal Name に該当します。

   class InsertWithoutNameReferenceInstruction {
       instruction_type:string = "insert_without_name_reference";

       huffman_encoded_name:boolean;

       name_length?:uint32;
       name?:string;

       huffman_encoded_value:boolean;

       value_length?:uint32;
       value?:string;
   }

DuplicateInstruction

Duplicate に関する定義です。

   class DuplicateInstruction {
       instruction_type:string = "duplicate";

       index:uint32;
   }

HeaderAcknowledgementInstruction

Section Acknowledgment に該当していそうです。

   class HeaderAcknowledgementInstruction {
       instruction_type:string = "header_acknowledgement";

       stream_id:uint64;
   }

StreamCancellationInstruction

Stream Cancellation に該当します。

   class StreamCancellationInstruction {
       instruction_type:string = "stream_cancellation";

       stream_id:uint64;
   }

InsertCountIncrementInstruction

Insert Count Increment に該当します。

   class InsertCountIncrementInstruction {
       instruction_type:string = "insert_count_increment";

       increment:uint32;
   }

QPACK Header compression

おそらく、Field Line Representations に該当します。

type QPackHeaderBlockRepresentation = IndexedHeaderField | LiteralHeaderFieldWithName | LiteralHeaderFieldWithoutName;

IndexedHeaderField

Indexed Field Line と、baseの値から動的テーブルのインデックスを識別する Indexed Field Line が含まれます。

class IndexedHeaderField {
    header_field_type:string = "indexed_header";

    table_type:"static"|"dynamic"; // MUST be "dynamic" if is_post_base is true
    index:uint32;

    is_post_base:boolean = false; // to represent the "indexed header field with post-base index" header field type
}

LiteralHeaderFieldWithName

Literal Field Line With Name Referenceと、Literal Field Line With Post-Base Name Reference が含まれます。

class LiteralHeaderFieldWithName {
    header_field_type:string = "literal_with_name";

    preserve_literal:boolean; // the 3rd "N" bit
    table_type:"static"|"dynamic"; // MUST be "dynamic" if is_post_base is true
    name_index:uint32;

    huffman_encoded_value:boolean;
    value_length?:uint32;
    value?:string;

    is_post_base:boolean = false; // to represent the "Literal header field with post-base name reference" header field type
}

LiteralHeaderFieldWithoutName

Literal Field Line With Literal Name のことではないかと思います。

   class LiteralHeaderFieldWithoutName {
       header_field_type:string = "literal_without_name";

       preserve_literal:boolean; // the 3rd "N" bit

       huffman_encoded_name:boolean;
       name_length?:uint32;
       name?:string;

       huffman_encoded_value:boolean;
       value_length?:uint32;
       value?:string;
   }

QPackHeaderBlockPrefix

Encoded Field Section Prefix ではないかと思います。

   class QPackHeaderBlockPrefix {
       required_insert_count:uint32;
       sign_bit:boolean;
       delta_base:uint32;
   }

終わりに

「HTTP/3 and QPACK event definitions for qlog」でこれから定義しようとしている、qlogにHTTP/3やQPACKのイベントを記録するための定義について紹介しました。

これから標準化が進んでいくと思われるので、今後も注目です。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?