aboutsummaryrefslogtreecommitdiffstats
path: root/asn1/sv
diff options
context:
space:
mode:
authorGerald Combs <gerald@wireshark.org>2010-06-01 23:27:16 +0000
committerGerald Combs <gerald@wireshark.org>2010-06-01 23:27:16 +0000
commitad273c35b6ba2800030812d80ada3599a439032f (patch)
tree9a89500524baf97b0731e47bd2929bf063ef3b6d /asn1/sv
parentc58a769956b9964676d8947e340a23066b4adf72 (diff)
From Michael Bernhard via bug 3398 with minor fixups:
I've created a ASN.1 dissector for the IEC 61850 Sampled Values protocol. It dissects ethernet frames of the IEC 61850-9-2LE specification form the UCA International User Group. There is also a new TAP for tshark (-R sv) which extracts the important information of the frame and allows to create plots (with external tools) of the sampled values. I've developed under Linux (Ubuntu 8.10) but everything should be in place for successful compilation under Windows. It would be great if this dissector could be included in wireshark. I'm looking forward for your comments. svn path=/trunk/; revision=33039
Diffstat (limited to 'asn1/sv')
-rw-r--r--asn1/sv/Makefile.am26
-rw-r--r--asn1/sv/Makefile.common47
-rw-r--r--asn1/sv/Makefile.nmake29
-rw-r--r--asn1/sv/packet-sv-template.c335
-rw-r--r--asn1/sv/packet-sv-template.h43
-rw-r--r--asn1/sv/sv.asn25
-rw-r--r--asn1/sv/sv.cnf34
7 files changed, 539 insertions, 0 deletions
diff --git a/asn1/sv/Makefile.am b/asn1/sv/Makefile.am
new file mode 100644
index 0000000000..0d2c3efcd4
--- /dev/null
+++ b/asn1/sv/Makefile.am
@@ -0,0 +1,26 @@
+# $Id:$
+#
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+include ../Makefile.preinc
+include Makefile.common
+include ../Makefile.inc
+
diff --git a/asn1/sv/Makefile.common b/asn1/sv/Makefile.common
new file mode 100644
index 0000000000..62bc168336
--- /dev/null
+++ b/asn1/sv/Makefile.common
@@ -0,0 +1,47 @@
+# $Id:$
+#
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+PROTOCOL_NAME=sv
+
+DISSECTOR_FILES= \
+ packet-$(PROTOCOL_NAME).c \
+ packet-$(PROTOCOL_NAME).h
+
+
+EXT_ASN_FILE_LIST =
+
+ASN_FILE_LIST = $(PROTOCOL_NAME).asn
+
+# The packet-$(PROTOCOL_NAME)-template.h and $(PROTOCOL_NAME).asn
+# files do not exist for all protocols: Please add/remove as required.
+EXTRA_DIST = \
+ Makefile.nmake \
+ $(ASN_FILE_LIST) \
+ packet-$(PROTOCOL_NAME)-template.c \
+ packet-$(PROTOCOL_NAME)-template.h \
+ $(PROTOCOL_NAME).cnf
+
+SRC_FILES = \
+ $(EXTRA_DIST) \
+ $(EXT_ASN_FILE_LIST)
+
+A2W_FLAGS= -b
diff --git a/asn1/sv/Makefile.nmake b/asn1/sv/Makefile.nmake
new file mode 100644
index 0000000000..8263722c5f
--- /dev/null
+++ b/asn1/sv/Makefile.nmake
@@ -0,0 +1,29 @@
+## Use: $(MAKE) /$(MAKEFLAGS) -f makefile.nmake
+#
+# $Id:$
+#
+#
+# Wireshark - Network traffic analyzer
+# By Gerald Combs <gerald@wireshark.org>
+# Copyright 1998 Gerald Combs
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+
+include ../../config.nmake
+include ../Makefile.preinc.nmake
+include Makefile.common
+include ../Makefile.inc.nmake
+
diff --git a/asn1/sv/packet-sv-template.c b/asn1/sv/packet-sv-template.c
new file mode 100644
index 0000000000..0096ea0319
--- /dev/null
+++ b/asn1/sv/packet-sv-template.c
@@ -0,0 +1,335 @@
+/* packet-sv.c
+ * Routines for IEC 61850 Sampled Vales packet dissection
+ * Michael Bernhard 2008
+ *
+ * $Id:$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <epan/packet.h>
+#include <epan/asn1.h>
+#include <epan/etypes.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "packet-ber.h"
+#include "packet-acse.h"
+
+#include "tap.h"
+
+#include "packet-sv.h"
+
+#define PNAME "IEC61850 Sampled Values"
+#define PSNAME "SV"
+#define PFNAME "sv"
+
+/* see IEC61850-8-1 8.2 */
+#define Q_VALIDITY_GOOD (0x0 << 0)
+#define Q_VALIDITY_INVALID (0x1 << 0)
+#define Q_VALIDITY_QUESTIONABLE (0x3 << 0)
+#define Q_VALIDITY_MASK (0x3 << 0)
+
+#define Q_OVERFLOW (1 << 2)
+#define Q_OUTOFRANGE (1 << 3)
+#define Q_BADREFERENCE (1 << 4)
+#define Q_OSCILLATORY (1 << 5)
+#define Q_FAILURE (1 << 6)
+#define Q_OLDDATA (1 << 7)
+#define Q_INCONSISTENT (1 << 8)
+#define Q_INACCURATE (1 << 9)
+
+#define Q_SOURCE_PROCESS (0 << 10)
+#define Q_SOURCE_SUBSTITUTED (1 << 10)
+#define Q_SOURCE_MASK (1 << 10)
+
+#define Q_TEST (1 << 11)
+#define Q_OPERATORBLOCKED (1 << 12)
+
+/* see UCA Implementation Guideline for IEC 61850-9-2 */
+#define Q_DERIVED (1 << 13)
+
+
+/* Data for SV tap */
+static int sv_tap = -1;
+static sv_frame_data sv_data;
+
+/* Initialize the protocol and registered fields */
+static int proto_sv = -1;
+static int hf_sv_appid = -1;
+static int hf_sv_length = -1;
+static int hf_sv_reserve1 = -1;
+static int hf_sv_reserve2 = -1;
+static int hf_sv_phmeas_instmag_i = -1;
+static int hf_sv_phsmeas_q = -1;
+static int hf_sv_phsmeas_q_validity = -1;
+static int hf_sv_phsmeas_q_overflow = -1;
+static int hf_sv_phsmeas_q_outofrange = -1;
+static int hf_sv_phsmeas_q_badreference = -1;
+static int hf_sv_phsmeas_q_oscillatory = -1;
+static int hf_sv_phsmeas_q_failure = -1;
+static int hf_sv_phsmeas_q_olddata = -1;
+static int hf_sv_phsmeas_q_inconsistent = -1;
+static int hf_sv_phsmeas_q_inaccurate = -1;
+static int hf_sv_phsmeas_q_source = -1;
+static int hf_sv_phsmeas_q_test = -1;
+static int hf_sv_phsmeas_q_operatorblocked = -1;
+static int hf_sv_phsmeas_q_derived = -1;
+
+#include "packet-sv-hf.c"
+
+/* Initialize the subtree pointers */
+static int ett_sv = -1;
+static int ett_phsmeas = -1;
+static int ett_phsmeas_q = -1;
+
+#include "packet-sv-ett.c"
+
+static const value_string sv_q_validity_vals[] = {
+ { 0, "good" },
+ { 1, "invalid" },
+ { 3, "questionable" },
+ { 0, NULL }
+};
+
+static const value_string sv_q_source_vals[] = {
+ { 0, "process" },
+ { 1, "substituted" },
+ { 0, NULL }
+};
+
+static int
+dissect_PhsMeas1(gboolean implicit_tag, packet_info *pinfo, proto_tree *tree, tvbuff_t *tvb, int offset, int hf_id _U_)
+{
+ gint8 class;
+ gboolean pc;
+ gint32 tag;
+ guint32 len;
+ proto_item *it;
+ proto_tree *subtree = NULL;
+ gint32 value;
+ guint32 qual;
+ guint32 i;
+
+ static const int *q_flags[] = {
+ &hf_sv_phsmeas_q_validity,
+ &hf_sv_phsmeas_q_overflow,
+ &hf_sv_phsmeas_q_outofrange,
+ &hf_sv_phsmeas_q_badreference,
+ &hf_sv_phsmeas_q_oscillatory,
+ &hf_sv_phsmeas_q_failure,
+ &hf_sv_phsmeas_q_olddata,
+ &hf_sv_phsmeas_q_inconsistent,
+ &hf_sv_phsmeas_q_inaccurate,
+ &hf_sv_phsmeas_q_source,
+ &hf_sv_phsmeas_q_test,
+ &hf_sv_phsmeas_q_operatorblocked,
+ &hf_sv_phsmeas_q_derived,
+ NULL
+ };
+
+ if (!implicit_tag) {
+ offset=dissect_ber_identifier(pinfo, tree, tvb, offset, &class, &pc, &tag);
+ offset=dissect_ber_length(pinfo, tree, tvb, offset, &len, NULL);
+ } else {
+ len=tvb_length_remaining(tvb, offset);
+ }
+
+ if (tree) {
+ it = proto_tree_add_text(tree, tvb, offset, len, "PhsMeas1");
+ subtree = proto_item_add_subtree(it, ett_phsmeas);
+ }
+
+ sv_data.num_phsMeas = 0;
+ for (i = 0; i < len/8; i++) {
+ if (tree && subtree) {
+ value = tvb_get_ntohl(tvb, offset);
+ qual = tvb_get_ntohl(tvb, offset + 4);
+
+ proto_tree_add_item(subtree, hf_sv_phmeas_instmag_i, tvb, offset, 4, FALSE);
+ proto_tree_add_bitmask(subtree, tvb, offset + 4, hf_sv_phsmeas_q, ett_phsmeas_q, q_flags, FALSE);
+
+ if (i < IEC61850_SV_MAX_PHSMEAS_ENTRIES) {
+ sv_data.phsMeas[i].value = value;
+ sv_data.phsMeas[i].qual = qual;
+ sv_data.num_phsMeas++;
+ }
+ }
+
+ offset += 8;
+ }
+
+ return offset;
+}
+
+#include "packet-sv-fn.c"
+
+/*
+* Dissect SV PDUs inside a PPDU.
+*/
+static void
+dissect_sv(tvbuff_t *tvb, packet_info *pinfo, proto_tree *parent_tree)
+{
+ int offset = 0;
+ int old_offset;
+ proto_item *item = NULL;
+ proto_tree *tree = NULL;
+ asn1_ctx_t asn1_ctx;
+
+ asn1_ctx_init(&asn1_ctx, ASN1_ENC_BER, TRUE, pinfo);
+
+ if (parent_tree){
+ item = proto_tree_add_item(parent_tree, proto_sv, tvb, 0, -1, FALSE);
+ tree = proto_item_add_subtree(item, ett_sv);
+ }
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, PNAME);
+ col_clear(pinfo->cinfo, COL_INFO);
+
+ /* APPID */
+ if (tree && tvb_reported_length_remaining(tvb, offset) >= 2)
+ proto_tree_add_item(tree, hf_sv_appid, tvb, offset, 2, FALSE);
+
+ /* Length */
+ if (tree && tvb_reported_length_remaining(tvb, offset) >= 4)
+ proto_tree_add_item(tree, hf_sv_length, tvb, offset + 2, 2, FALSE);
+
+ /* Reserved 1 */
+ if (tree && tvb_reported_length_remaining(tvb, offset) >= 6)
+ proto_tree_add_item(tree, hf_sv_reserve1, tvb, offset + 4, 2, FALSE);
+
+ /* Reserved 2 */
+ if (tree && tvb_reported_length_remaining(tvb, offset) >= 8)
+ proto_tree_add_item(tree, hf_sv_reserve2, tvb, offset + 6, 2, FALSE);
+
+ offset = 8;
+ while (tree && tvb_reported_length_remaining(tvb, offset) > 0){
+ old_offset = offset;
+ offset = dissect_sv_SampledValues(FALSE, tvb, offset, &asn1_ctx , tree, -1);
+ if (offset == old_offset) {
+ proto_tree_add_text(tree, tvb, offset, -1, "Internal error, zero-byte SV PDU");
+ offset = tvb_length(tvb);
+ break;
+ }
+ }
+
+ if(tree)
+ tap_queue_packet(sv_tap, pinfo, &sv_data);
+}
+
+
+/*--- proto_register_sv -------------------------------------------*/
+void proto_register_sv(void) {
+
+ /* List of fields */
+ static hf_register_info hf[] = {
+ { &hf_sv_appid,
+ { "APPID", "sv.appid", FT_UINT16, BASE_HEX, NULL, 0x0, NULL, HFILL }},
+
+ { &hf_sv_length,
+ { "Length", "sv.length", FT_UINT16, BASE_DEC, NULL, 0x0, NULL, HFILL }},
+
+ { &hf_sv_reserve1,
+ { "Reserved 1", "sv.reserve1", FT_UINT16, BASE_HEX_DEC, NULL, 0x0, NULL, HFILL }},
+
+ { &hf_sv_reserve2,
+ { "Reserved 2", "sv.reserve2", FT_UINT16, BASE_HEX_DEC, NULL, 0x0, NULL, HFILL }},
+
+ { &hf_sv_phmeas_instmag_i,
+ { "value", "sv.meas_value", FT_INT32, BASE_DEC, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q,
+ { "quality", "sv.meas_quality", FT_UINT32, BASE_HEX, NULL, 0x0, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_validity,
+ { "validity", "sv.meas_quality.validity", FT_UINT32, BASE_HEX, VALS(sv_q_validity_vals), Q_VALIDITY_MASK, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_overflow,
+ { "overflow", "sv.meas_quality.overflow", FT_BOOLEAN, 32, NULL, Q_OVERFLOW, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_outofrange,
+ { "out of range", "sv.meas_quality.outofrange", FT_BOOLEAN, 32, NULL, Q_OUTOFRANGE, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_badreference,
+ { "bad reference", "sv.meas_quality.badreference", FT_BOOLEAN, 32, NULL, Q_BADREFERENCE, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_oscillatory,
+ { "oscillatory", "sv.meas_quality.oscillatory", FT_BOOLEAN, 32, NULL, Q_OSCILLATORY, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_failure,
+ { "failure", "sv.meas_quality.failure", FT_BOOLEAN, 32, NULL, Q_FAILURE, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_olddata,
+ { "old data", "sv.meas_quality.olddata", FT_BOOLEAN, 32, NULL, Q_OLDDATA, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_inconsistent,
+ { "inconsistent", "sv.meas_quality.inconsistent", FT_BOOLEAN, 32, NULL, Q_INCONSISTENT, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_inaccurate,
+ { "inaccurate", "sv.meas_quality.inaccurate", FT_BOOLEAN, 32, NULL, Q_INACCURATE, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_source,
+ { "source", "sv.meas_quality.source", FT_UINT32, BASE_HEX, VALS(sv_q_source_vals), Q_SOURCE_MASK, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_test,
+ { "test", "sv.meas_quality.teset", FT_BOOLEAN, 32, NULL, Q_TEST, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_operatorblocked,
+ { "operator blocked", "sv.meas_quality.operatorblocked", FT_BOOLEAN, 32, NULL, Q_OPERATORBLOCKED, NULL, HFILL}},
+
+ { &hf_sv_phsmeas_q_derived,
+ { "derived", "sv.meas_quality.derived", FT_BOOLEAN, 32, NULL, Q_DERIVED, NULL, HFILL}},
+
+
+#include "packet-sv-hfarr.c"
+ };
+
+ /* List of subtrees */
+ static gint *ett[] = {
+ &ett_sv,
+ &ett_phsmeas,
+ &ett_phsmeas_q,
+#include "packet-sv-ettarr.c"
+ };
+
+ /* Register protocol */
+ proto_sv = proto_register_protocol(PNAME, PSNAME, PFNAME);
+ register_dissector("sv", dissect_sv, proto_sv);
+
+ /* Register fields and subtrees */
+ proto_register_field_array(proto_sv, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+
+ /* Register tap */
+ sv_tap = register_tap("sv");
+}
+
+/*--- proto_reg_handoff_sv --- */
+void proto_reg_handoff_sv(void) {
+
+ dissector_handle_t sv_handle;
+ sv_handle = find_dissector("sv");
+
+ dissector_add("ethertype", ETHERTYPE_IEC61850_SV, sv_handle);
+}
diff --git a/asn1/sv/packet-sv-template.h b/asn1/sv/packet-sv-template.h
new file mode 100644
index 0000000000..557a026df8
--- /dev/null
+++ b/asn1/sv/packet-sv-template.h
@@ -0,0 +1,43 @@
+/* packet-sv.h
+ * Routines for IEC 61850 Sampled Vales packet dissection
+ * Michael Bernhard 2008
+ *
+ * $Id:$
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __PACKET_SV_H__
+#define __PACKET_SV_H__
+
+#define IEC61850_SV_MAX_PHSMEAS_ENTRIES 20
+
+typedef struct _sv_phs_meas {
+ gint32 value;
+ guint32 qual;
+} sv_phs_meas;
+
+typedef struct _sv_frame_data {
+ guint16 smpCnt;
+ guint8 smpSynch;
+ guint8 num_phsMeas;
+ sv_phs_meas phsMeas[IEC61850_SV_MAX_PHSMEAS_ENTRIES];
+} sv_frame_data;
+
+#endif /*__PACKET_SV_H__*/
diff --git a/asn1/sv/sv.asn b/asn1/sv/sv.asn
new file mode 100644
index 0000000000..9aac7683ef
--- /dev/null
+++ b/asn1/sv/sv.asn
@@ -0,0 +1,25 @@
+-- $Id:$
+IEC61850 DEFINITIONS ::= BEGIN
+
+SampledValues ::= CHOICE {
+ savPdu [APPLICATION 0] IMPLICIT SavPdu,
+ ...
+}
+
+SavPdu ::= SEQUENCE {
+ noASDU [0] IMPLICIT INTEGER(0..65535),
+ seqASDU [2] IMPLICIT SEQUENCE OF ASDU
+}
+
+ASDU ::= SEQUENCE {
+ svID [0] IMPLICIT VisibleString,
+ smpCnt [2] IMPLICIT INTEGER(0..65535),
+ confRef [3] IMPLICIT INTEGER(0..4294967295),
+ smpSynch [5] IMPLICIT INTEGER{none(0),local(1),global(2)},
+ seqData [7] IMPLICIT Data,
+ ...
+}
+
+Data ::= OCTET STRING
+
+END
diff --git a/asn1/sv/sv.cnf b/asn1/sv/sv.cnf
new file mode 100644
index 0000000000..698f37e79e
--- /dev/null
+++ b/asn1/sv/sv.cnf
@@ -0,0 +1,34 @@
+# sv.cnf
+# SampledValue conformation file
+
+# $Id:$
+
+#.MODULE_IMPORT
+
+#.EXPORTS
+
+#.PDU
+
+#.NO_EMIT
+
+#.TYPE_RENAME
+
+#.FIELD_RENAME
+
+#.FN_BODY ASDU/smpCnt
+ guint32 value;
+ offset = dissect_ber_integer(implicit_tag, actx, tree, tvb, offset, hf_index, &value);
+ sv_data.smpCnt = value;
+#.END
+
+#.FN_BODY ASDU/smpSynch
+ guint32 value;
+ offset = dissect_ber_integer(implicit_tag, actx, tree, tvb, offset, hf_index, &value);
+ sv_data.smpSynch = value;
+#.END
+
+#.FN_BODY Data
+ offset = dissect_PhsMeas1(implicit_tag, actx->pinfo, tree, tvb, offset, hf_index);
+#.END
+
+#.END_OF_CNF \ No newline at end of file